diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7c0a768..b89e7ca 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
- Added `defaultProtocol` configuration option as fallback if no config is provided.
### Changed
- set `odata` as the service protocol if no protocol is set to match the default behavior of `@sap/cds`. Provide `cds.env.openapi.defaultProtocol = "rest"` to restore old behavior.
+- ER diagrams are now rendered using the yuml.me v1 API (`app.yuml.me`), avoiding the 'please migrate' image. The diagram and legend are displayed as clickable SVG images.
### Deprecated
### Removed
### Fixed
diff --git a/lib/compile/diagram.js b/lib/compile/diagram.js
index 0b8ce3a..e9e0c7b 100644
--- a/lib/compile/diagram.js
+++ b/lib/compile/diagram.js
@@ -59,25 +59,28 @@ class Diagram {
Object.keys(entityContainer).filter(name => isIdentifier(name)).reverse().forEach(name => {
const resource = entityContainer[name];
if (resource.$Type) {
- diagram += `${this.#comma}[${name}%20${this.#color.resource}]` // additional space in case entity set and type have same name
+ diagram += `${this.#comma}[${name}\u200C${this.#color.resource}]` // U+200C (ZWNJ) disambiguates entity set node from same-named entity type node
+ `++-${cardinality(resource)}>[${nameParts(resource.$Type).name}]`;
} else if (resource.$Action) {
- diagram += `${this.#comma}[${name}${this.#color.resource}]`;
- const overload = this.#meta.modelElement(resource.$Action).find(pOverload => !pOverload.$IsBound);
- diagram += this.#overloadDiagram(name, overload);
- } else if (resource.$Function) {
- diagram += `${this.#comma}[${name}${this.#color.resource}]`;
- const overloads = this.#meta.modelElement(resource.$Function);
- if (overloads) {
- const unbound = overloads.filter(overload => !overload.$IsBound);
- // TODO: loop over all overloads, add new source box after first arrow
- diagram += this.#overloadDiagram(name, unbound[0]);
- }
+ diagram += `${this.#comma}[${name}${this.#color.resource}]`;
+ const overload = this.#meta.modelElement(resource.$Action).find(pOverload => !pOverload.$IsBound);
+ diagram += this.#overloadDiagram(name, overload);
+ } else if (resource.$Function) {
+ diagram += `${this.#comma}[${name}${this.#color.resource}]`;
+ const overloads = this.#meta.modelElement(resource.$Function);
+ if (overloads) {
+ const unbound = overloads.filter(overload => !overload.$IsBound);
+ // TODO: loop over all overloads, add new source box after first arrow
+ diagram += this.#overloadDiagram(name, unbound[0]);
}
+ }
});
if (diagram != '') {
- diagram = `\n\n## Entity Data Model\n\n\n### Legend\n`;
+ const legendDsl = `@direction LR\n[External.Type${this.#color.external}]-[ComplexType${this.#color.complexType}]-[EntityType${this.#color.entityType}]-[EntitySet/Singleton/Operation${this.#color.resource}]`;
+ const erUrl = `https://app.yuml.me/diagram/v1/class/plain/${encodeURIComponent(diagram)}.svg`;
+ const legendUrl = `https://app.yuml.me/diagram/v1/class/plain/${encodeURIComponent(legendDsl)}.svg`;
+ diagram = `\n\n### Entity Data Model\n[](${erUrl})\n\n#### Legend\n
`;
}
return diagram;
diff --git a/test/lib/compile/csdl2openapi.test.js b/test/lib/compile/csdl2openapi.test.js
index e50aec8..2f516e3 100644
--- a/test/lib/compile/csdl2openapi.test.js
+++ b/test/lib/compile/csdl2openapi.test.js
@@ -2404,11 +2404,11 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
[
"Use the Core.LongDescription or Core.Description annotation on your CDS service to provide a meaningful description.",
"",
- "## Entity Data Model",
- "",
+ "### Entity Data Model",
+ "[](https://app.yuml.me/diagram/v1/class/plain/%5Broot%7Bbg%3Alightslategray%7D%5D%2C%5Broot%5D-%3E%5Bother%5D%2C%5Bother%7Bbg%3Alightslategray%7D%5D%2C%5Bact%7Bbg%3Alawngreen%7D%5D-%3E%5Broot%5D%2C%5Bact%7Bbg%3Alawngreen%7D%5Din-%3E%5Broot%5D%2C%5Bothers%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5Bother%5D%2C%5Broots%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5Broot%5D.svg)",
"",
- "### Legend",
- "",
+ "#### Legend",
+ "
",
],
"diagram"
);
diff --git a/test/lib/compile/data/Northwind-key-as-segment.openapi3.json b/test/lib/compile/data/Northwind-key-as-segment.openapi3.json
index d70f87d..6949dae 100644
--- a/test/lib/compile/data/Northwind-key-as-segment.openapi3.json
+++ b/test/lib/compile/data/Northwind-key-as-segment.openapi3.json
@@ -2,7 +2,7 @@
"openapi": "3.0.2",
"info": {
"title": "Use the title annotation on your CDS service to provide a meaningful title.",
- "description": "Use the Core.LongDescription or Core.Description annotation on your CDS service to provide a meaningful description.\n\n## Entity Data Model\n\n\n### Legend\n",
+ "description": "Use the Core.LongDescription or Core.Description annotation on your CDS service to provide a meaningful description.\n\n### Entity Data Model\n[](https://app.yuml.me/diagram/v1/class/plain/%5BCategory%7Bbg%3Alightslategray%7D%5D%2C%5BCustomerDemographic%7Bbg%3Alightslategray%7D%5D%2C%5BCustomer%7Bbg%3Alightslategray%7D%5D%2C%5BCustomer%5D*-*%5BCustomerDemographic%5D%2C%5BEmployee%7Bbg%3Alightslategray%7D%5D%2C%5BEmployee%5D*-0..1%5BEmployee%5D%2C%5BOrder_Detail%7Bbg%3Alightslategray%7D%5D%2C%5BOrder_Detail%5D*-%5BOrder%5D%2C%5BOrder%7Bbg%3Alightslategray%7D%5D%2C%5BOrder%5D*-0..1%5BCustomer%5D%2C%5BOrder%5D*-0..1%5BEmployee%5D%2C%5BProduct%7Bbg%3Alightslategray%7D%5D%2C%5BProduct%5D*-0..1%5BCategory%5D%2C%5BProduct%5D-*%5BOrder_Detail%5D%2C%5BRegion%7Bbg%3Alightslategray%7D%5D%2C%5BShipper%7Bbg%3Alightslategray%7D%5D%2C%5BShipper%5D0..1-*%5BOrder%5D%2C%5BSupplier%7Bbg%3Alightslategray%7D%5D%2C%5BSupplier%5D0..1-*%5BProduct%5D%2C%5BTerritory%7Bbg%3Alightslategray%7D%5D%2C%5BTerritory%5D*-%5BRegion%5D%2C%5BTerritory%5D*-*%5BEmployee%5D%2C%5BAlphabetical_list_of_product%7Bbg%3Alightslategray%7D%5D%2C%5BCategory_Sales_for_1997%7Bbg%3Alightslategray%7D%5D%2C%5BCurrent_Product_List%7Bbg%3Alightslategray%7D%5D%2C%5BCustomer_and_Suppliers_by_City%7Bbg%3Alightslategray%7D%5D%2C%5BInvoice%7Bbg%3Alightslategray%7D%5D%2C%5BOrder_Details_Extended%7Bbg%3Alightslategray%7D%5D%2C%5BOrder_Subtotal%7Bbg%3Alightslategray%7D%5D%2C%5BOrders_Qry%7Bbg%3Alightslategray%7D%5D%2C%5BProduct_Sales_for_1997%7Bbg%3Alightslategray%7D%5D%2C%5BProducts_Above_Average_Price%7Bbg%3Alightslategray%7D%5D%2C%5BProducts_by_Category%7Bbg%3Alightslategray%7D%5D%2C%5BSales_by_Category%7Bbg%3Alightslategray%7D%5D%2C%5BSales_Totals_by_Amount%7Bbg%3Alightslategray%7D%5D%2C%5BSummary_of_Sales_by_Quarter%7Bbg%3Alightslategray%7D%5D%2C%5BSummary_of_Sales_by_Year%7Bbg%3Alightslategray%7D%5D%2C%5BSummary_of_Sales_by_Years%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BSummary_of_Sales_by_Year%5D%2C%5BSummary_of_Sales_by_Quarters%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BSummary_of_Sales_by_Quarter%5D%2C%5BSales_Totals_by_Amounts%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BSales_Totals_by_Amount%5D%2C%5BSales_by_Categories%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BSales_by_Category%5D%2C%5BProducts_by_Categories%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BProducts_by_Category%5D%2C%5BProducts_Above_Average_Prices%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BProducts_Above_Average_Price%5D%2C%5BProduct_Sales_for_1997%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BProduct_Sales_for_1997%5D%2C%5BOrders_Qries%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BOrders_Qry%5D%2C%5BOrder_Subtotals%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BOrder_Subtotal%5D%2C%5BOrder_Details_Extendeds%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BOrder_Details_Extended%5D%2C%5BInvoices%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BInvoice%5D%2C%5BCustomer_and_Suppliers_by_Cities%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BCustomer_and_Suppliers_by_City%5D%2C%5BCurrent_Product_Lists%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BCurrent_Product_List%5D%2C%5BCategory_Sales_for_1997%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BCategory_Sales_for_1997%5D%2C%5BAlphabetical_list_of_products%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BAlphabetical_list_of_product%5D%2C%5BTerritories%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BTerritory%5D%2C%5BSuppliers%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BSupplier%5D%2C%5BShippers%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BShipper%5D%2C%5BRegions%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BRegion%5D%2C%5BProducts%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BProduct%5D%2C%5BOrders%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BOrder%5D%2C%5BOrder_Details%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BOrder_Detail%5D%2C%5BEmployees%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BEmployee%5D%2C%5BCustomers%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BCustomer%5D%2C%5BCustomerDemographics%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BCustomerDemographic%5D%2C%5BCategories%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BCategory%5D.svg)\n\n#### Legend\n
",
"version": ""
},
"x-sap-api-type": "ODATAV4",
diff --git a/test/lib/compile/data/People.openapi3.json b/test/lib/compile/data/People.openapi3.json
index df2ecbc..adbfb48 100644
--- a/test/lib/compile/data/People.openapi3.json
+++ b/test/lib/compile/data/People.openapi3.json
@@ -2,7 +2,7 @@
"openapi": "3.0.2",
"info": {
"title": "Use the title annotation on your CDS service to provide a meaningful title.",
- "description": "Use the Core.LongDescription or Core.Description annotation on your CDS service to provide a meaningful description.\n\n## Entity Data Model\n\n\n### Legend\n",
+ "description": "Use the Core.LongDescription or Core.Description annotation on your CDS service to provide a meaningful description.\n\n### Entity Data Model\n[](https://app.yuml.me/diagram/v1/class/plain/%5BSupplier%7Bbg%3Alightslategray%7D%5D%2C%5BSupplier%5D%2B%2B-0..1%5BAddress%5D%2C%5BSupplier%5D-*%3E%5BProductService.Product%7Bbg%3Awhitesmoke%7D%5D%2C%5BAddress%5D%2C%5BPerson%7Bbg%3Alightslategray%7D%5D%2C%5BPerson%5D%5E%5BCustomer%7Bbg%3Alightslategray%7D%5D%2C%5BPerson%5D%5E%5BEmployee%7Bbg%3Alightslategray%7D%5D%2C%5BPersonDetail%7Bbg%3Alightslategray%7D%5D%2C%5BPersonDetail%5D%2B%2B-0..1%5BAddress%5D%2C%5BPersonDetail%5D0..1-0..1%5BPerson%5D%2C%5BPersonDetails%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BPersonDetail%5D%2C%5BPeople%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BPerson%5D%2C%5BSuppliers%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BSupplier%5D.svg)\n\n#### Legend\n
",
"version": ""
},
"x-sap-api-type": "ODATAV4",
@@ -737,6 +737,68 @@
}
}
},
+ "/PersonDetails({PersonID})/Photo": {
+ "parameters": [
+ {
+ "description": "key: PersonID",
+ "in": "path",
+ "name": "PersonID",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "int32"
+ }
+ }
+ ],
+ "get": {
+ "summary": "Retrieves photo of a single person detail.",
+ "tags": [
+ "Person Details"
+ ],
+ "responses": {
+ "200": {
+ "description": "Retrieved photo",
+ "content": {
+ "*/*": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "4XX": {
+ "$ref": "#/components/responses/error"
+ }
+ }
+ },
+ "put": {
+ "summary": "Changes photo of a single person detail.",
+ "tags": [
+ "Person Details"
+ ],
+ "requestBody": {
+ "description": "New value for photo",
+ "required": true,
+ "content": {
+ "*/*": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Success"
+ },
+ "4XX": {
+ "$ref": "#/components/responses/error"
+ }
+ }
+ }
+ },
"/Suppliers": {
"get": {
"summary": "Retrieves a list of suppliers.",
diff --git a/test/lib/compile/data/Products.openapi3.json b/test/lib/compile/data/Products.openapi3.json
index 9c2c67b..52b8026 100644
--- a/test/lib/compile/data/Products.openapi3.json
+++ b/test/lib/compile/data/Products.openapi3.json
@@ -2,7 +2,7 @@
"openapi": "3.0.2",
"info": {
"title": "Use the title annotation on your CDS service to provide a meaningful title.",
- "description": "Use the Core.LongDescription or Core.Description annotation on your CDS service to provide a meaningful description.\n\n## Entity Data Model\n\n\n### Legend\n",
+ "description": "Use the Core.LongDescription or Core.Description annotation on your CDS service to provide a meaningful description.\n\n### Entity Data Model\n[](https://app.yuml.me/diagram/v1/class/plain/%5BProduct%7Bbg%3Alightslategray%7D%5D%2C%5BProduct%5D*-*%5BCategory%5D%2C%5BProduct%5D-0..1%3E%5BPeopleService.Supplier%7Bbg%3Awhitesmoke%7D%5D%2C%5BProduct%5D%5E%5BFeaturedProduct%7Bbg%3Alightslategray%7D%5D%2C%5BFeaturedProduct%5D0..1-0..1%5BAdvertisement%5D%2C%5BProductDetail%7Bbg%3Alightslategray%7D%5D%2C%5BProductDetail%5D0..1-0..1%5BProduct%5D%2C%5BCategory%7Bbg%3Alightslategray%7D%5D%2C%5BAdvertisement%7Bbg%3Alightslategray%7D%5D%2C%5BAdvertisements%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BAdvertisement%5D%2C%5BCategories%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BCategory%5D%2C%5BProductDetails%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BProductDetail%5D%2C%5BProducts%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BProduct%5D.svg)\n\n#### Legend\n
",
"version": ""
},
"x-sap-api-type": "ODATAV4",
@@ -322,6 +322,69 @@
}
}
},
+ "/Advertisements({ID})/$value": {
+ "parameters": [
+ {
+ "description": "key: ID",
+ "in": "path",
+ "name": "ID",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "example": "01234567-89ab-cdef-0123-456789abcdef"
+ }
+ }
+ ],
+ "get": {
+ "summary": "Retrieves the media stream of a single advertisement.",
+ "tags": [
+ "Advertisements"
+ ],
+ "responses": {
+ "200": {
+ "description": "Retrieved advertisement",
+ "content": {
+ "*/*": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "4XX": {
+ "$ref": "#/components/responses/error"
+ }
+ }
+ },
+ "put": {
+ "summary": "Changes the media stream of a single advertisement.",
+ "tags": [
+ "Advertisements"
+ ],
+ "requestBody": {
+ "description": "New media stream for advertisement",
+ "required": true,
+ "content": {
+ "*/*": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Success"
+ },
+ "4XX": {
+ "$ref": "#/components/responses/error"
+ }
+ }
+ }
+ },
"/Advertisements({ID})/FeaturedProduct": {
"parameters": [
{
diff --git a/test/lib/compile/data/TripPin.openapi3.json b/test/lib/compile/data/TripPin.openapi3.json
index 31bb1a5..beacf93 100644
--- a/test/lib/compile/data/TripPin.openapi3.json
+++ b/test/lib/compile/data/TripPin.openapi3.json
@@ -2,7 +2,7 @@
"openapi": "3.0.2",
"info": {
"title": "TripPin service is a sample service for OData V4.",
- "description": "This service is located at [https://services.odata.org/V4/(S(cnbm44wtbc1v5bgrlek5lpcc))/TripPinServiceRW/](https://services.odata.org/V4/%28S%28cnbm44wtbc1v5bgrlek5lpcc%29%29/TripPinServiceRW/)\n\n## Entity Data Model\n\n\n### Legend\n",
+ "description": "This service is located at [https://services.odata.org/V4/(S(cnbm44wtbc1v5bgrlek5lpcc))/TripPinServiceRW/](https://services.odata.org/V4/%28S%28cnbm44wtbc1v5bgrlek5lpcc%29%29/TripPinServiceRW/)\n\n### Entity Data Model\n[](https://app.yuml.me/diagram/v1/class/plain/%5BCity%5D%2C%5BLocation%5D%2C%5BLocation%5D%2B%2B-%5BCity%5D%2C%5BLocation%5D%5E%5BEventLocation%5D%2C%5BLocation%5D%5E%5BAirportLocation%5D%2C%5BPhoto%7Bbg%3Alightslategray%7D%5D%2C%5BPerson%7Bbg%3Alightslategray%7D%5D%2C%5BPerson%5D%2B%2B-*%5BLocation%5D%2C%5BPerson%5D%2B%2B-0..1%5BPersonGender%5D%2C%5BPerson%5D-*%3E%5BPerson%5D%2C%5BPerson%5D%2B%2B-*%3E%5BTrip%5D%2C%5BPerson%5D-0..1%3E%5BPhoto%5D%2C%5BAirline%7Bbg%3Alightslategray%7D%5D%2C%5BAirport%7Bbg%3Alightslategray%7D%5D%2C%5BAirport%5D%2B%2B-%5BAirportLocation%5D%2C%5BPlanItem%7Bbg%3Alightslategray%7D%5D%2C%5BPlanItem%5D%5E%5BPublicTransportation%7Bbg%3Alightslategray%7D%5D%2C%5BPublicTransportation%5D%5E%5BFlight%7Bbg%3Alightslategray%7D%5D%2C%5BFlight%5D-%3E%5BAirport%5D%2C%5BFlight%5D-%3E%5BAirport%5D%2C%5BFlight%5D-%3E%5BAirline%5D%2C%5BPlanItem%5D%5E%5BEvent%7Bbg%3Alightslategray%7D%5D%2C%5BEvent%5D%2B%2B-%5BEventLocation%5D%2C%5BTrip%7Bbg%3Alightslategray%7D%5D%2C%5BTrip%5D-*%3E%5BPhoto%5D%2C%5BTrip%5D%2B%2B-*%3E%5BPlanItem%5D%2C%5BResetDataSource%7Bbg%3Alawngreen%7D%5D%2C%5BGetNearestAirport%7Bbg%3Alawngreen%7D%5D-%3E%5BAirport%5D%2C%5BMe%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-%3E%5BPerson%5D%2C%5BAirports%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BAirport%5D%2C%5BAirlines%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BAirline%5D%2C%5BPeople%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BPerson%5D%2C%5BPhotos%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BPhoto%5D.svg)\n\n#### Legend\n
",
"version": ""
},
"x-sap-api-type": "ODATAV4",
diff --git a/test/lib/compile/data/aggregation.openapi3.json b/test/lib/compile/data/aggregation.openapi3.json
index 3f3b3d5..3905fb3 100644
--- a/test/lib/compile/data/aggregation.openapi3.json
+++ b/test/lib/compile/data/aggregation.openapi3.json
@@ -2,7 +2,7 @@
"openapi": "3.0.2",
"info": {
"title": "Use the title annotation on your CDS service to provide a meaningful title.",
- "description": "Use the Core.LongDescription or Core.Description annotation on your CDS service to provide a meaningful description.\n\n## Entity Data Model\n\n\n### Legend\n",
+ "description": "Use the Core.LongDescription or Core.Description annotation on your CDS service to provide a meaningful description.\n\n### Entity Data Model\n[](https://app.yuml.me/diagram/v1/class/plain/%5BCategory%7Bbg%3Alightslategray%7D%5D%2C%5BCategory%5D-*%3E%5BProduct%5D%2C%5BCurrency%7Bbg%3Alightslategray%7D%5D%2C%5BCustomer%7Bbg%3Alightslategray%7D%5D%2C%5BCustomer%5D-*%3E%5BSales%5D%2C%5BProduct%7Bbg%3Alightslategray%7D%5D%2C%5BProduct%5D-0..1%3E%5BCategory%5D%2C%5BProduct%5D-*%3E%5BSales%5D%2C%5BSales%7Bbg%3Alightslategray%7D%5D%2C%5BSales%5D-%3E%5BCustomer%5D%2C%5BSales%5D-%3E%5BTime%5D%2C%5BSales%5D-%3E%5BProduct%5D%2C%5BSales%5D-%3E%5BSalesOrganization%5D%2C%5BSales%5D-%3E%5BCurrency%5D%2C%5BSalesOrganization%7Bbg%3Alightslategray%7D%5D%2C%5BSalesOrganization%5D-0..1%3E%5BSalesOrganization%5D%2C%5BTime%7Bbg%3Alightslategray%7D%5D%2C%5BCurrencies%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BCurrency%5D%2C%5BSales%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BSales%5D.svg)\n\n#### Legend\n
",
"version": ""
},
"x-sap-api-type": "ODATAV4",
diff --git a/test/lib/compile/data/annotations.openapi3.json b/test/lib/compile/data/annotations.openapi3.json
index ac36152..b22e980 100644
--- a/test/lib/compile/data/annotations.openapi3.json
+++ b/test/lib/compile/data/annotations.openapi3.json
@@ -2,7 +2,7 @@
"openapi": "3.0.2",
"info": {
"title": "Service for namespace Supported.Annotations",
- "description": "This service is located at [https://localhost/service-root/](https://localhost/service-root/)\n\n## Entity Data Model\n\n\n### Legend\n",
+ "description": "This service is located at [https://localhost/service-root/](https://localhost/service-root/)\n\n### Entity Data Model\n[](https://app.yuml.me/diagram/v1/class/plain/%5BSinglePartKey%7Bbg%3Alightslategray%7D%5D%2C%5BSinglePartKey%5D-0..1%3E%5BTwoPartKey%5D%2C%5BSinglePartKey%5D-*%3E%5BTwoPartKey%5D%2C%5BSinglePartKey%5D-0..1%3E%5BTwoPartKey%5D%2C%5BSinglePartKey%5D-*%3E%5BTwoPartKey%5D%2C%5BSinglePartKey%5D-0..1%3E%5BTwoPartKey%5D%2C%5BSinglePartKey%5D-*%3E%5BTwoPartKey%5D%2C%5BTwoPartKey%7Bbg%3Alightslategray%7D%5D%2C%5BTwoNothingSingleton%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-%3E%5BTwoPartKey%5D%2C%5BTwoWriteOnlySingleton%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-%3E%5BTwoPartKey%5D%2C%5BTwoNothingSet%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BTwoPartKey%5D%2C%5BTwoWriteOnlySet%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BTwoPartKey%5D%2C%5BTwoReadOnlyByKeySet%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BTwoPartKey%5D%2C%5BTwoReadOnlySet%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BTwoPartKey%5D%2C%5BTwoAllSet%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BTwoPartKey%5D%2C%5BReadOnlySingleton%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-%3E%5BSinglePartKey%5D%2C%5BAllSingleton%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-%3E%5BSinglePartKey%5D%2C%5BReadListOnlySet%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BSinglePartKey%5D%2C%5BOneNavigationSet%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BSinglePartKey%5D%2C%5BNoNavigationSet%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BSinglePartKey%5D%2C%5BAllSet%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BSinglePartKey%5D.svg)\n\n#### Legend\n
",
"version": ""
},
"x-sap-api-type": "ODATAV4",
diff --git a/test/lib/compile/data/authorization.openapi3.json b/test/lib/compile/data/authorization.openapi3.json
index d5141f4..6b6434e 100644
--- a/test/lib/compile/data/authorization.openapi3.json
+++ b/test/lib/compile/data/authorization.openapi3.json
@@ -2,7 +2,7 @@
"openapi": "3.0.2",
"info": {
"title": "Service for namespace auth.example",
- "description": "This service is located at [https://localhost/service-root/](https://localhost/service-root/)\n\n## Entity Data Model\n\n\n### Legend\n",
+ "description": "This service is located at [https://localhost/service-root/](https://localhost/service-root/)\n\n### Entity Data Model\n[](https://app.yuml.me/diagram/v1/class/plain/%5BPerson%7Bbg%3Alightslategray%7D%5D%2C%5BPeople%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BPerson%5D.svg)\n\n#### Legend\n
",
"version": ""
},
"x-sap-api-type": "ODATAV4",
diff --git a/test/lib/compile/data/autoexposed-direct.openapi3.json b/test/lib/compile/data/autoexposed-direct.openapi3.json
index 2e8021a..d1cb590 100644
--- a/test/lib/compile/data/autoexposed-direct.openapi3.json
+++ b/test/lib/compile/data/autoexposed-direct.openapi3.json
@@ -675,4 +675,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/test/lib/compile/data/containment.openapi3.json b/test/lib/compile/data/containment.openapi3.json
index 458a9e7..9be0370 100644
--- a/test/lib/compile/data/containment.openapi3.json
+++ b/test/lib/compile/data/containment.openapi3.json
@@ -2,7 +2,7 @@
"openapi": "3.0.2",
"info": {
"title": "Service for namespace Containment",
- "description": "This service is located at [https://localhost/service-root/](https://localhost/service-root/)\n\n## Entity Data Model\n\n\n### Legend\n",
+ "description": "This service is located at [https://localhost/service-root/](https://localhost/service-root/)\n\n### Entity Data Model\n[](https://app.yuml.me/diagram/v1/class/plain/%5BWhole%7Bbg%3Alightslategray%7D%5D%2C%5BWhole%5D%2B%2B-0..1%3E%5BPart%5D%2C%5BWhole%5D%2B%2B-*%3E%5BPart%5D%2C%5BPart%7Bbg%3Alightslategray%7D%5D%2C%5BPart%5D%2B%2B-0..1%3E%5BSubPart%5D%2C%5BPart%5D%2B%2B-*%3E%5BSubPart%5D%2C%5BPart%5D%2B%2B-*%3E%5BSubPart%5D%2C%5BPart%5D%2B%2B-*%3E%5BSubPart%5D%2C%5BSubPart%7Bbg%3Alightslategray%7D%5D%2C%5BFolder%7Bbg%3Alightslategray%7D%5D%2C%5BFolder%5D%2B%2B-*%3E%5BFolder%5D%2C%5BFolders%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BFolder%5D%2C%5BTheWhole%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-%3E%5BWhole%5D%2C%5BWholes%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BWhole%5D.svg)\n\n#### Legend\n
",
"version": ""
},
"x-sap-api-type": "ODATAV4",
diff --git a/test/lib/compile/data/csdl-16.1.openapi3.json b/test/lib/compile/data/csdl-16.1.openapi3.json
index 9daaa5b..80d3f1f 100644
--- a/test/lib/compile/data/csdl-16.1.openapi3.json
+++ b/test/lib/compile/data/csdl-16.1.openapi3.json
@@ -2,7 +2,7 @@
"openapi": "3.0.2",
"info": {
"title": "Service for namespace ODataDemo",
- "description": "This service is located at [https://localhost/service-root/](https://localhost/service-root/)\n\n## Entity Data Model\n\n\n### Legend\n",
+ "description": "This service is located at [https://localhost/service-root/](https://localhost/service-root/)\n\n### Entity Data Model\n[](https://app.yuml.me/diagram/v1/class/plain/%5BProduct%7Bbg%3Alightslategray%7D%5D%2C%5BProduct%5D*-%5BCategory%5D%2C%5BCategory%7Bbg%3Alightslategray%7D%5D%2C%5BSupplier%7Bbg%3Alightslategray%7D%5D%2C%5BSupplier%5D%2B%2B-%5BAddress%5D%2C%5BSupplier%5D0..1-*%5BProduct%5D%2C%5BCountry%7Bbg%3Alightslategray%7D%5D%2C%5BAddress%5D%2C%5BAddress%5D-0..1%3E%5BCountry%5D%2C%5BProductsByRating%7Bbg%3Alawngreen%7D%5D-*%3E%5BProduct%5D%2C%5BCountries%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BCountry%5D%2C%5BMainSupplier%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-%3E%5BSupplier%5D%2C%5BSuppliers%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BSupplier%5D%2C%5BCategories%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BCategory%5D%2C%5BProducts%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BProduct%5D.svg)\n\n#### Legend\n
",
"version": ""
},
"x-sap-api-type": "ODATAV4",
diff --git a/test/lib/compile/data/custom-parameters.openapi3.json b/test/lib/compile/data/custom-parameters.openapi3.json
index a194804..efebe4b 100644
--- a/test/lib/compile/data/custom-parameters.openapi3.json
+++ b/test/lib/compile/data/custom-parameters.openapi3.json
@@ -2,7 +2,7 @@
"openapi": "3.0.2",
"info": {
"title": "Service for namespace custom.parameters",
- "description": "This service is located at [https://localhost/service-root/](https://localhost/service-root/)\n\n## Entity Data Model\n\n\n### Legend\n",
+ "description": "This service is located at [https://localhost/service-root/](https://localhost/service-root/)\n\n### Entity Data Model\n[](https://app.yuml.me/diagram/v1/class/plain/%5Bent%7Bbg%3Alightslategray%7D%5D%2C%5Bfunc%7Bbg%3Alawngreen%7D%5D-0..1%3E%5Bent%5D%2C%5Bact%7Bbg%3Alawngreen%7D%5D-0..1%3E%5Bent%5D%2C%5Bsing%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-0..1%3E%5Bent%5D%2C%5Bents%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5Bent%5D.svg)\n\n#### Legend\n
",
"version": ""
},
"x-sap-api-type": "ODATAV4",
diff --git a/test/lib/compile/data/description-fallback.openapi3.json b/test/lib/compile/data/description-fallback.openapi3.json
index 63ec641..257f14f 100644
--- a/test/lib/compile/data/description-fallback.openapi3.json
+++ b/test/lib/compile/data/description-fallback.openapi3.json
@@ -2,7 +2,7 @@
"openapi": "3.0.2",
"info": {
"title": "Use the title annotation on your CDS service to provide a meaningful title.",
- "description": "Service with only Description annotation\n\n## Entity Data Model\n\n\n### Legend\n",
+ "description": "Service with only Description annotation\n\n### Entity Data Model\n[](https://app.yuml.me/diagram/v1/class/plain/%5BBook%7Bbg%3Alightslategray%7D%5D%2C%5BdoSomething%7Bbg%3Alawngreen%7D%5D%2C%5BBooks%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BBook%5D.svg)\n\n#### Legend\n
",
"version": ""
},
"x-sap-api-type": "ODATAV4",
diff --git a/test/lib/compile/data/descriptions.openapi3.json b/test/lib/compile/data/descriptions.openapi3.json
index 7bba0a5..05cae56 100644
--- a/test/lib/compile/data/descriptions.openapi3.json
+++ b/test/lib/compile/data/descriptions.openapi3.json
@@ -2,7 +2,7 @@
"openapi": "3.0.2",
"info": {
"title": "Container - Label",
- "description": "Container - LongDescription\n\n## Entity Data Model\n\n\n### Legend\n",
+ "description": "Container - LongDescription\n\n### Entity Data Model\n[](https://app.yuml.me/diagram/v1/class/plain/%5Bentity%7Bbg%3Alightslategray%7D%5D%2C%5Bentity%5D%2B%2B-*%3E%5BsubEntity%5D%2C%5Bentity%5D-*%3E%5Bentity%5D%2C%5BsubEntity%7Bbg%3Alightslategray%7D%5D%2C%5Bfunctionimport_no_descriptions%7Bbg%3Alawngreen%7D%5D%2C%5Bfunctionimport_ext%7Bbg%3Alawngreen%7D%5D%2C%5Bfunctionimport%7Bbg%3Alawngreen%7D%5D%2C%5Bactionimport_no_descriptions%7Bbg%3Alawngreen%7D%5D%2C%5Bactionimport_ext%7Bbg%3Alawngreen%7D%5D%2C%5Bactionimport%7Bbg%3Alawngreen%7D%5D%2C%5Bsingle_ext%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-%3E%5Bentity%5D%2C%5Bsingle%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-0..1%3E%5Bentity%5D%2C%5Bentities_ext%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5Bentity%5D%2C%5Bentities%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5Bentity%5D.svg)\n\n#### Legend\n
",
"version": "0.0.1"
},
"x-sap-api-type": "ODATAV4",
diff --git a/test/lib/compile/data/example.openapi3.json b/test/lib/compile/data/example.openapi3.json
index f592216..b29eed5 100644
--- a/test/lib/compile/data/example.openapi3.json
+++ b/test/lib/compile/data/example.openapi3.json
@@ -2,7 +2,7 @@
"openapi": "3.0.2",
"info": {
"title": "Use the title annotation on your CDS service to provide a meaningful title.",
- "description": "Use the Core.LongDescription or Core.Description annotation on your CDS service to provide a meaningful description.\n\n## Entity Data Model\n\n\n### Legend\n",
+ "description": "Use the Core.LongDescription or Core.Description annotation on your CDS service to provide a meaningful description.\n\n### Entity Data Model\n[](https://app.yuml.me/diagram/v1/class/plain/%5BProduct%7Bbg%3Alightslategray%7D%5D%2C%5BProduct%5D*-*%5BCategory%5D%2C%5BProduct%5D%5E%5BFeaturedProduct%7Bbg%3Alightslategray%7D%5D%2C%5BFeaturedProduct%5D0..1-0..1%5BAdvertisement%5D%2C%5BProductDetail%7Bbg%3Alightslategray%7D%5D%2C%5BProductDetail%5D0..1-0..1%5BProduct%5D%2C%5BCategory%7Bbg%3Alightslategray%7D%5D%2C%5BSupplier%7Bbg%3Alightslategray%7D%5D%2C%5BSupplier%5D%2B%2B-0..1%5BAddress%5D%2C%5BSupplier%5D0..1-*%5BProduct%5D%2C%5BAddress%5D%2C%5BPerson%7Bbg%3Alightslategray%7D%5D%2C%5BPerson%5D%5E%5BCustomer%7Bbg%3Alightslategray%7D%5D%2C%5BPerson%5D%5E%5BEmployee%7Bbg%3Alightslategray%7D%5D%2C%5BPersonDetail%7Bbg%3Alightslategray%7D%5D%2C%5BPersonDetail%5D%2B%2B-0..1%5BAddress%5D%2C%5BPersonDetail%5D0..1-0..1%5BPerson%5D%2C%5BAdvertisement%7Bbg%3Alightslategray%7D%5D%2C%5BIncreaseSalaries%7Bbg%3Alawngreen%7D%5D%2C%5BAdvertisements%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BAdvertisement%5D%2C%5BPersonDetails%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BPersonDetail%5D%2C%5BPersons%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BPerson%5D%2C%5BSuppliers%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BSupplier%5D%2C%5BCategories%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BCategory%5D%2C%5BProductDetails%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BProductDetail%5D%2C%5BProducts%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BProduct%5D.svg)\n\n#### Legend\n
",
"version": ""
},
"x-sap-api-type": "ODATAV4",
@@ -331,6 +331,69 @@
}
}
},
+ "/Advertisements({ID})/$value": {
+ "parameters": [
+ {
+ "description": "key: ID",
+ "in": "path",
+ "name": "ID",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "format": "uuid",
+ "example": "01234567-89ab-cdef-0123-456789abcdef"
+ }
+ }
+ ],
+ "get": {
+ "summary": "Retrieves the media stream of a single advertisement.",
+ "tags": [
+ "Advertisements"
+ ],
+ "responses": {
+ "200": {
+ "description": "Retrieved advertisement",
+ "content": {
+ "*/*": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "4XX": {
+ "$ref": "#/components/responses/error"
+ }
+ }
+ },
+ "put": {
+ "summary": "Changes the media stream of a single advertisement.",
+ "tags": [
+ "Advertisements"
+ ],
+ "requestBody": {
+ "description": "New media stream for advertisement",
+ "required": true,
+ "content": {
+ "*/*": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Success"
+ },
+ "4XX": {
+ "$ref": "#/components/responses/error"
+ }
+ }
+ }
+ },
"/Advertisements({ID})/FeaturedProduct": {
"parameters": [
{
@@ -1225,6 +1288,68 @@
}
}
},
+ "/PersonDetails({PersonID})/Photo": {
+ "parameters": [
+ {
+ "description": "key: PersonID",
+ "in": "path",
+ "name": "PersonID",
+ "required": true,
+ "schema": {
+ "type": "integer",
+ "format": "int32"
+ }
+ }
+ ],
+ "get": {
+ "summary": "Retrieves photo of a single person detail.",
+ "tags": [
+ "Person Details"
+ ],
+ "responses": {
+ "200": {
+ "description": "Retrieved photo",
+ "content": {
+ "*/*": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "4XX": {
+ "$ref": "#/components/responses/error"
+ }
+ }
+ },
+ "put": {
+ "summary": "Changes photo of a single person detail.",
+ "tags": [
+ "Person Details"
+ ],
+ "requestBody": {
+ "description": "New value for photo",
+ "required": true,
+ "content": {
+ "*/*": {
+ "schema": {
+ "type": "string",
+ "format": "binary"
+ }
+ }
+ }
+ },
+ "responses": {
+ "204": {
+ "description": "Success"
+ },
+ "4XX": {
+ "$ref": "#/components/responses/error"
+ }
+ }
+ }
+ },
"/Persons": {
"get": {
"summary": "Retrieves a list of persons.",
diff --git a/test/lib/compile/data/immutable-composition.openapi3.json b/test/lib/compile/data/immutable-composition.openapi3.json
index 1444ff6..20891d4 100644
--- a/test/lib/compile/data/immutable-composition.openapi3.json
+++ b/test/lib/compile/data/immutable-composition.openapi3.json
@@ -2,7 +2,7 @@
"openapi": "3.0.2",
"info": {
"title": "Use the title annotation on your CDS service to provide a meaningful title.",
- "description": "Use the Core.LongDescription or Core.Description annotation on your CDS service to provide a meaningful description.\n\n## Entity Data Model\n\n\n### Legend\n",
+ "description": "Use the Core.LongDescription or Core.Description annotation on your CDS service to provide a meaningful description.\n\n### Entity Data Model\n[](https://app.yuml.me/diagram/v1/class/plain/%5BParent%7Bbg%3Alightslategray%7D%5D%2C%5BParent%5D%2B%2B-%3E%5BChild%5D%2C%5BParent%5D%2B%2B-%3E%5BChild%5D%2C%5BChild%7Bbg%3Alightslategray%7D%5D%2C%5BParents%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BParent%5D.svg)\n\n#### Legend\n
",
"version": ""
},
"x-sap-api-type": "ODATAV4",
diff --git a/test/lib/compile/data/miscellaneous.openapi3.json b/test/lib/compile/data/miscellaneous.openapi3.json
index 54cacad..3aaf6dd 100644
--- a/test/lib/compile/data/miscellaneous.openapi3.json
+++ b/test/lib/compile/data/miscellaneous.openapi3.json
@@ -2,7 +2,7 @@
"openapi": "3.0.2",
"info": {
"title": "Use the title annotation on your CDS service to provide a meaningful title.",
- "description": "Examples from Part 3: CSDL\n\n## Entity Data Model\n\n\n### Legend\n",
+ "description": "Examples from Part 3: CSDL\n\n### Entity Data Model\n[](https://app.yuml.me/diagram/v1/class/plain/%5BMeasurement%5D%2C%5BExample7to11%5D%2C%5BProduct%7Bbg%3Alightslategray%7D%5D%2C%5BProduct%5D*-%5BCategory%5D%2C%5BProduct%5D-0..1%3E%5BSupplier%5D%2C%5BSupplier%7Bbg%3Alightslategray%7D%5D%2C%5BCustomer%7Bbg%3Alightslategray%7D%5D%2C%5BOrder%7Bbg%3Alightslategray%7D%5D%2C%5BCategory%7Bbg%3Alightslategray%7D%5D%2C%5BEmployee%7Bbg%3Alightslategray%7D%5D%2C%5BEmployee%5D-0..1%3E%5BManager%5D%2C%5BEmployee%5D%5E%5BManager%7Bbg%3Alightslategray%7D%5D%2C%5BManager%5D-*%3E%5BEmployee%5D%2C%5BCategory17%7Bbg%3Alightslategray%7D%5D%2C%5BCategory18%7Bbg%3Alightslategray%7D%5D%2C%5BCategory18%5D%2B%2B-%5BEntityInfo%5D%2C%5BEntityInfo%5D%2C%5BOrderLine%7Bbg%3Alightslategray%7D%5D%2C%5BDimensions%5D%2C%5BProduct22%7Bbg%3Alightslategray%7D%5D%2C%5BProduct22%5D%2B%2B-0..1%5BDimensions%5D%2C%5BProduct22%5D%2B%2B-0..1%5BDimensions%5D%2C%5BShipmentBox%7Bbg%3Alightslategray%7D%5D%2C%5BShipmentBox%5D%2B%2B-0..1%5BDimensions%5D%2C%5BSize%5D%2C%5BSize%5D%2B%2B-0..1%5BLength%5D%2C%5BSize%5D%2B%2B-0..1%5BWeight%5D%2C%5BProduct2%7Bbg%3Alightslategray%7D%5D%2C%5BNullablePrimitiveTypes%5D%2C%5BNullablePrimitiveTypes%5D%2B%2B-0..1%5BColor%5D%2C%5BNullablePrimitiveTypes%5D%2B%2B-0..1%5BIntegerDecimal%5D%2C%5BNonNullablePrimitiveTypes%5D%2C%5BNonNullablePrimitiveTypes%5D%2B%2B-%5BColor%5D%2C%5BNonNullablePrimitiveTypes%5D%2B%2B-%5BIntegerDecimal%5D%2C%5BNonNullablePrimitiveTypes%5D%2B%2B-%5BText%5D%2C%5BWeird%7Bbg%3Alightslategray%7D%5D%2C%5BWeird%5D%2B%2B-%5BCore.Permission%7Bbg%3Awhitesmoke%7D%5D%2C%5BWeird%5D%2B%2B-0..1%5BCore.Permission%7Bbg%3Awhitesmoke%7D%5D%2C%5BAddress%5D%2C%5BAddress%5D-0..1%3E%5BCity%5D%2C%5BAddress%5D-0..1%3E%5BRegion%5D%2C%5BAddress%5D-0..1%3E%5BCountry%5D%2C%5BCity%7Bbg%3Alightslategray%7D%5D%2C%5BRegion%7Bbg%3Alightslategray%7D%5D%2C%5BCountry%7Bbg%3Alightslategray%7D%5D%2C%5BPlanet%7Bbg%3Alightslategray%7D%5D%2C%5BNavigationOnly%5D%2C%5BNavigationOnly%5D-0..1%3E%5BPlanet%5D%2C%5BAnnotationOnly%5D%2C%5BOrder%7Bbg%3Alightslategray%7D%5D%2C%5BPersonType%5D%2C%5BMovieType%5D%2C%5BFriendType%7Bbg%3Alightslategray%7D%5D%2C%5BPersonType%5D%5E%5BDeveloperType%5D%2C%5BScrumTeamType%5D%2C%5BScrumTeamType%5D%2B%2B-0..1%5BDeveloperType%5D%2C%5BMovieType%5D%5E%5BArchivedMovieType%5D%2C%5BSomeThingType%5D%2C%5BSomeThingType%5D%2B%2B-*%5BPersonType%5D%2C%5BSomeThingType%5D-*%3E%5BFriendType%5D%2C%5BChangeTrackingType%5D%2C%5BCategories35%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BCategory%5D%2C%5BCategories34%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BCategory%5D%2C%5BProductsByRating%7Bbg%3Alawngreen%7D%5D-*%3E%5BProduct%5D%2C%5BLeaveRequestApproval%7Bbg%3Alawngreen%7D%5D%2C%5BMainSupplier%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-%3E%5BSupplier%5D%2C%5BSuppliers%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BSupplier%5D%2C%5BCategories%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BCategory%5D%2C%5BProducts%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BProduct%5D%2C%5BTopSellingProducts%7Bbg%3Alawngreen%7D%5D-*%3E%5BProduct%5D%2C%5BOrders%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BOrder%5D%2C%5BPreferredCustomers%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BCustomer%5D%2C%5BStandardCustomers%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BCustomer%5D.svg)\n\n#### Legend\n
",
"version": ""
},
"x-sap-api-type": "ODATAV4",
diff --git a/test/lib/compile/data/non-expandable.openapi3.json b/test/lib/compile/data/non-expandable.openapi3.json
index 00955a6..1a20a3e 100644
--- a/test/lib/compile/data/non-expandable.openapi3.json
+++ b/test/lib/compile/data/non-expandable.openapi3.json
@@ -2,7 +2,7 @@
"openapi": "3.0.2",
"info": {
"title": "Use the title annotation on your CDS service to provide a meaningful title.",
- "description": "Use the Core.LongDescription or Core.Description annotation on your CDS service to provide a meaningful description.\n\n## Entity Data Model\n\n\n### Legend\n",
+ "description": "Use the Core.LongDescription or Core.Description annotation on your CDS service to provide a meaningful description.\n\n### Entity Data Model\n[](https://app.yuml.me/diagram/v1/class/plain/%5BBooks%7Bbg%3Alightslategray%7D%5D%2C%5BBooks%5D%2B%2B-*%3E%5BPages%5D%2C%5BPages%7Bbg%3Alightslategray%7D%5D%2C%5BPages%5D-%3E%5BBooks%5D%2C%5BPages%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BPages%5D%2C%5BBooks%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BBooks%5D.svg)\n\n#### Legend\n
",
"version": ""
},
"x-sap-api-type": "ODATAV4",
diff --git a/test/lib/compile/data/parameter-with-both-descriptions.openapi3.json b/test/lib/compile/data/parameter-with-both-descriptions.openapi3.json
index 069c7d6..48fcfe9 100644
--- a/test/lib/compile/data/parameter-with-both-descriptions.openapi3.json
+++ b/test/lib/compile/data/parameter-with-both-descriptions.openapi3.json
@@ -2,7 +2,7 @@
"openapi": "3.0.2",
"info": {
"title": "Use the title annotation on your CDS service to provide a meaningful title.",
- "description": "Use the Core.LongDescription or Core.Description annotation on your CDS service to provide a meaningful description.\n\n## Entity Data Model\n\n\n### Legend\n",
+ "description": "Use the Core.LongDescription or Core.Description annotation on your CDS service to provide a meaningful description.\n\n### Entity Data Model\n[](https://app.yuml.me/diagram/v1/class/plain/%5BItem%7Bbg%3Alightslategray%7D%5D%2C%5BprocessItem%7Bbg%3Alawngreen%7D%5D%2C%5BItems%E2%80%8C%7Bbg%3Alawngreen%7D%5D%2B%2B-*%3E%5BItem%5D.svg)\n\n#### Legend\n
",
"version": ""
},
"x-sap-api-type": "ODATAV4",
diff --git a/test/lib/compile/diagram.test.js b/test/lib/compile/diagram.test.js
new file mode 100644
index 0000000..b3d0e50
--- /dev/null
+++ b/test/lib/compile/diagram.test.js
@@ -0,0 +1,253 @@
+const { describe, test } = require('node:test');
+const assert = require('node:assert');
+const { Diagram } = require('../../../lib/compile/diagram');
+const { CSDLMeta } = require('../../../lib/compile/csdl');
+
+function makeDiagram(csdl) {
+ return new Diagram(new CSDLMeta(csdl));
+}
+
+/** Extract the DSL string from the first yuml.me URL in the diagram markdown. */
+function extractDsl(diagramMarkdown) {
+ // Match everything between /plain/ and the last .svg before ) or ]
+ const match = diagramMarkdown.match(/\/plain\/(.+?)\.svg(?=[)\]])/);
+ return match ? decodeURIComponent(match[1]) : '';
+}
+
+describe('Diagram', () => {
+ describe('getResourceDiagram', () => {
+ test('returns empty string for empty entity container', () => {
+ const csdl = {
+ $Version: '4.01',
+ 'My.Service': { $Kind: 'Schema', EntityContainer: { $Kind: 'EntityContainer' } },
+ };
+ assert.strictEqual(makeDiagram(csdl).getResourceDiagram({}), '');
+ });
+
+ test('generates diagram markdown with heading, ER link and legend for non-empty container', () => {
+ const csdl = {
+ $Version: '4.01',
+ 'My.Service': { Order: { $Kind: 'EntityType' } },
+ };
+ const result = makeDiagram(csdl).getResourceDiagram({
+ Orders: { $Type: 'My.Service.Order', $Collection: true },
+ });
+ assert.ok(result.includes('### Entity Data Model'), 'should include heading');
+ assert.ok(result.includes('ER Diagram'), 'should include ER diagram link');
+ assert.ok(result.includes('Legend'), 'should include legend');
+ });
+
+ test('entity type uses lightslategray color', () => {
+ const csdl = {
+ $Version: '4.01',
+ 'My.Service': { Order: { $Kind: 'EntityType' } },
+ };
+ const dsl = extractDsl(makeDiagram(csdl).getResourceDiagram({
+ Orders: { $Type: 'My.Service.Order', $Collection: true },
+ }));
+ assert.ok(dsl.includes('[Order{bg:lightslategray}]'), 'entity type should carry lightslategray color');
+ });
+
+ test('complex type is included without entity color', () => {
+ const csdl = {
+ $Version: '4.01',
+ 'My.Service': {
+ Address: { $Kind: 'ComplexType' },
+ Customer: {
+ $Kind: 'EntityType',
+ HomeAddress: { $Type: 'My.Service.Address' },
+ },
+ },
+ };
+ const dsl = extractDsl(makeDiagram(csdl).getResourceDiagram({
+ Customers: { $Type: 'My.Service.Customer', $Collection: true },
+ }));
+ assert.ok(dsl.includes('[Address]'), 'complex type should appear without extra color');
+ assert.ok(!dsl.includes('[Address{'), 'complex type should not have a color annotation');
+ });
+
+ test('inheritance is depicted with base-type arrow', () => {
+ const csdl = {
+ $Version: '4.01',
+ 'My.Service': {
+ Base: { $Kind: 'EntityType' },
+ Child: { $Kind: 'EntityType', $BaseType: 'My.Service.Base' },
+ },
+ };
+ const dsl = extractDsl(makeDiagram(csdl).getResourceDiagram({
+ Children: { $Type: 'My.Service.Child', $Collection: true },
+ }));
+ assert.ok(dsl.includes('[Base]^[Child'), 'should show ^-arrow from base type');
+ });
+
+ test('navigation property links source and target type', () => {
+ const csdl = {
+ $Version: '4.01',
+ 'My.Service': {
+ Order: {
+ $Kind: 'EntityType',
+ Items: { $Kind: 'NavigationProperty', $Type: 'My.Service.Item', $Collection: true },
+ },
+ Item: { $Kind: 'EntityType' },
+ },
+ };
+ const dsl = extractDsl(makeDiagram(csdl).getResourceDiagram({
+ Orders: { $Type: 'My.Service.Order', $Collection: true },
+ }));
+ assert.ok(dsl.includes('[Order]'), 'should include Order type');
+ assert.ok(dsl.includes('[Item]'), 'should include Item type');
+ assert.ok(dsl.includes('[Order]-'), 'should have arrow from Order');
+ });
+
+ test('containment navigation property uses ++ notation', () => {
+ const csdl = {
+ $Version: '4.01',
+ 'My.Service': {
+ Order: {
+ $Kind: 'EntityType',
+ Items: { $Kind: 'NavigationProperty', $Type: 'My.Service.Item', $Collection: true, $ContainsTarget: true },
+ },
+ Item: { $Kind: 'EntityType' },
+ },
+ };
+ const dsl = extractDsl(makeDiagram(csdl).getResourceDiagram({
+ Orders: { $Type: 'My.Service.Order', $Collection: true },
+ }));
+ assert.ok(dsl.includes('[Order]++-'), 'containment should use ++ notation');
+ });
+
+ test('entity set node is disambiguated from entity type node with ZWNJ', () => {
+ const csdl = {
+ $Version: '4.01',
+ 'My.Service': { Order: { $Kind: 'EntityType' } },
+ };
+ const dsl = extractDsl(makeDiagram(csdl).getResourceDiagram({
+ Orders: { $Type: 'My.Service.Order', $Collection: true },
+ }));
+ assert.ok(dsl.includes('Orders'), 'entity set node should carry ZWNJ (U+200C)');
+ });
+
+ test('singleton resource node uses lawngreen color', () => {
+ const csdl = {
+ $Version: '4.01',
+ 'My.Service': { Config: { $Kind: 'EntityType' } },
+ };
+ const dsl = extractDsl(makeDiagram(csdl).getResourceDiagram({
+ AppConfig: { $Type: 'My.Service.Config' },
+ }));
+ assert.ok(dsl.includes('AppConfig{bg:lawngreen}'), 'singleton should use lawngreen');
+ });
+
+ test('unbound action import node appears in diagram', () => {
+ const csdl = {
+ $Version: '4.01',
+ 'My.Service': {
+ ResetAll: [{ $Kind: 'Action' }],
+ },
+ };
+ const dsl = extractDsl(makeDiagram(csdl).getResourceDiagram({
+ Reset: { $Action: 'My.Service.ResetAll' },
+ }));
+ assert.ok(dsl.includes('[Reset{bg:lawngreen}]'), 'action import should appear as resource node');
+ });
+
+ test('unbound function import node appears in diagram', () => {
+ const csdl = {
+ $Version: '4.01',
+ 'My.Service': {
+ TopItems: [{ $Kind: 'Function', $ReturnType: { $Type: 'My.Service.Item', $Collection: true } }],
+ Item: { $Kind: 'EntityType' },
+ },
+ };
+ const dsl = extractDsl(makeDiagram(csdl).getResourceDiagram({
+ BestItems: { $Function: 'My.Service.TopItems' },
+ }));
+ assert.ok(dsl.includes('[BestItems{bg:lawngreen}]'), 'function import should appear as resource node');
+ });
+
+ test('generated ER URL points to yuml.me and is properly encoded', () => {
+ const csdl = {
+ $Version: '4.01',
+ 'My.Service': { Order: { $Kind: 'EntityType' } },
+ };
+ const result = makeDiagram(csdl).getResourceDiagram({
+ Orders: { $Type: 'My.Service.Order', $Collection: true },
+ });
+ assert.ok(result.includes('app.yuml.me'), 'should point to yuml.me');
+ const urlMatch = result.match(/\(https:\/\/app\.yuml\.me[^)]+\)/);
+ assert.ok(urlMatch, 'should contain a URL in parentheses');
+ assert.ok(!urlMatch[0].includes(' '), 'URL should not contain unencoded spaces');
+ });
+
+ test('bidirectional nav property is depicted only once', () => {
+ const csdl = {
+ $Version: '4.01',
+ 'My.Service': {
+ Order: {
+ $Kind: 'EntityType',
+ Items: { $Kind: 'NavigationProperty', $Type: 'My.Service.Item', $Collection: true, $Partner: 'Order' },
+ },
+ Item: {
+ $Kind: 'EntityType',
+ Order: { $Kind: 'NavigationProperty', $Type: 'My.Service.Order', $Partner: 'Items' },
+ },
+ },
+ };
+ const dsl = extractDsl(makeDiagram(csdl).getResourceDiagram({
+ Orders: { $Type: 'My.Service.Order', $Collection: true },
+ }));
+ const arrows = (dsl.match(/\[Order\][^,]*\[Item\]|\[Item\][^,]*\[Order\]/g) || []);
+ assert.strictEqual(arrows.length, 1, 'bidirectional nav property should produce exactly one arrow');
+ });
+
+ test('collection nav property shows * cardinality', () => {
+ const csdl = {
+ $Version: '4.01',
+ 'My.Service': {
+ Order: {
+ $Kind: 'EntityType',
+ Items: { $Kind: 'NavigationProperty', $Type: 'My.Service.Item', $Collection: true },
+ },
+ Item: { $Kind: 'EntityType' },
+ },
+ };
+ const dsl = extractDsl(makeDiagram(csdl).getResourceDiagram({
+ Orders: { $Type: 'My.Service.Order', $Collection: true },
+ }));
+ assert.ok(dsl.includes('-*>'), 'collection nav property should show * cardinality');
+ });
+
+ test('nullable nav property shows 0..1 cardinality', () => {
+ const csdl = {
+ $Version: '4.01',
+ 'My.Service': {
+ Order: {
+ $Kind: 'EntityType',
+ Customer: { $Kind: 'NavigationProperty', $Type: 'My.Service.CustomerType', $Nullable: true },
+ },
+ CustomerType: { $Kind: 'EntityType' },
+ },
+ };
+ const dsl = extractDsl(makeDiagram(csdl).getResourceDiagram({
+ Orders: { $Type: 'My.Service.Order', $Collection: true },
+ }));
+ assert.ok(dsl.includes('-0..1>'), 'nullable nav property should show 0..1 cardinality');
+ });
+
+ test('external type (unresolved) carries whitesmoke background', () => {
+ const csdl = {
+ $Version: '4.01',
+ 'My.Service': {
+ Order: {
+ $Kind: 'EntityType',
+ Ext: { $Type: 'External.SomeType' },
+ },
+ },
+ };
+ const dsl = extractDsl(makeDiagram(csdl).getResourceDiagram({
+ Orders: { $Type: 'My.Service.Order', $Collection: true },
+ }));
+ assert.ok(dsl.includes('whitesmoke'), 'unresolved external type should carry whitesmoke color');
+ });
+ });
+});