niwe-odata-openapi
Version:
Convert OData CSDL XML or CSDL JSON to OpenAPI
192 lines (180 loc) • 5.8 kB
JavaScript
/**
* Resource diagram for an OData model using https://yuml.me
*
* Latest version: https://github.com/oasis-tcs/odata-openapi/blob/main/lib/edm.js
*/
const { nameParts } = require("./edm");
module.exports = { resourceDiagram };
/**
* Construct resource diagram
* @param {object} model EDM for CSDL document
* @return {string} resource diagram
*/
function resourceDiagram(model, rootResourcesToKeep, entityTypesToKeep) {
let diagram = "";
let comma = "";
//TODO: make colors configurable
let color = {
resource: "{bg:lawngreen}",
entityType: "{bg:lightslategray}",
complexType: "",
external: "{bg:whitesmoke}",
};
// entityTypesToKeep contains alias-qualified names, model.structuredTypes contains namespace-qualified names
const aliasQualifiedTypesToKeep = entityTypesToKeep
? entityTypesToKeep.map((name) => model.namespaceQualifiedName(name))
: null;
for (const [qualifiedName, type] of model.structuredTypes) {
if (
aliasQualifiedTypesToKeep &&
!aliasQualifiedTypesToKeep.includes(qualifiedName)
)
continue;
const typeName = nameParts(qualifiedName).name;
diagram +=
comma +
(type.$BaseType
? "[" +
nameParts(type.$BaseType).name +
(type.$Kind == "EntityType" ? color.entityType : color.complexType) +
"]^"
: "") +
"[" +
typeName +
(type.$Kind == "EntityType" ? color.entityType : color.complexType) +
"]";
for (const [name, property] of model.directPropertiesOfStructuredType(
type,
)) {
const targetNP = nameParts(property.$Type ?? "Edm.String");
if (
property.$Kind == "NavigationProperty" ||
targetNP.qualifier != "Edm"
) {
const target = model.element(property.$Type);
const bidirectional =
property.$Partner &&
target &&
target[property.$Partner] &&
target[property.$Partner].$Partner == name;
// Note: if the partner has the same name then it will also be depicted
if (!bidirectional || name <= property.$Partner) {
diagram +=
",[" +
typeName +
"]" +
(property.$Kind != "NavigationProperty" || property.$ContainsTarget
? "++"
: bidirectional
? cardinality(target[property.$Partner])
: "") +
"-" +
cardinality(property) +
(property.$Kind != "NavigationProperty" || bidirectional
? ""
: ">") +
"[" +
(target
? targetNP.name +
(target.$Kind == "EntityType"
? color.entityType
: color.complexType)
: property.$Type + color.external) +
"]";
}
}
}
comma = ",";
}
for (const [name, resource] of model.resources.reverse()) {
if (rootResourcesToKeep && !rootResourcesToKeep.includes(name)) continue;
if (resource.$Type) {
diagram +=
comma +
"[" +
name +
"%20" +
color.resource +
"]" + // additional space in case entity set and type have same name
"++-" +
cardinality(resource) +
">[" +
nameParts(resource.$Type).name +
"]";
} else {
if (resource.$Action) {
diagram += comma + "[" + name + color.resource + "]";
const overload = model
.element(resource.$Action)
.find((o) => !o.$IsBound);
diagram += overloadDiagram(name, overload);
} else if (resource.$Function) {
diagram += comma + "[" + name + color.resource + "]";
const overloads = model.element(resource.$Function);
if (overloads) {
const unbound = overloads.filter((o) => !o.$IsBound);
// TODO: loop over all overloads, add new source box after first arrow
diagram += overloadDiagram(name, unbound[0]);
}
}
}
comma = ",";
}
if (diagram != "") {
diagram =
"\n\n## Entity Data Model\n\n\n### Legend\n";
}
return diagram;
/**
* Diagram representation of property cardinality
* @param {object} typedElement Typed model element, e.g. property
* @param {boolean} one Explicitly represent to-1
* @return {string} cardinality
*/
function cardinality(typedElement) {
return typedElement.$Collection
? "*"
: typedElement.$Nullable
? "0..1"
: "";
}
function overloadDiagram(name, overload) {
let diag = "";
if (overload.$ReturnType) {
const type = model.element(overload.$ReturnType.$Type ?? "Edm.String");
if (type) {
diag +=
"-" +
cardinality(overload.$ReturnType, true) +
">[" +
nameParts(overload.$ReturnType.$Type).name +
"]";
}
}
for (const param of overload.$Parameter ?? []) {
const type = model.element(param.$Type ?? "Edm.String");
if (type) {
diag +=
comma +
"[" +
name +
"]in-" +
cardinality(param.$Type) +
">[" +
nameParts(param.$Type).name +
"]";
}
}
return diag;
}
}