Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect name collisions #93

Merged
merged 5 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 0.9.2 2024-01-24

### Added

- `xml2json`: detect name collisions of schemas, schema children, type children, and entity container children

## 0.9.0 2024-01-24

### Added
Expand Down
50 changes: 46 additions & 4 deletions lib/xml2json.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

//TODO:
// - read vocabulary to find out type of term to correctly determine "empty" value
// - extract message-stuff into a "logger"
// - extract preParser or at least checkElement* functions and their constants

const sax = require("sax");

Expand Down Expand Up @@ -189,7 +191,7 @@ module.exports.xml2json = function (
break;
}

checkXmlNamespace(node, this);
checkElementNamespace(node, this);
};

preParser.onclosetag = function (tag) {
Expand Down Expand Up @@ -236,8 +238,8 @@ module.exports.xml2json = function (
Reference: {
x: true,
v2x: true,
Include: { min: 0 },
Annotation: { min: 0 },
Include: { min: 0 },
IncludeAnnotations: { min: 0 },
},
Include: { x: true, v2x: true, Annotation: { min: 0 } },
Expand Down Expand Up @@ -518,7 +520,7 @@ module.exports.xml2json = function (
* @param {Object} node The current XML node
* @param {Object} parser XML parser instance
*/
function checkXmlNamespace(node, parser) {
function checkElementNamespace(node, parser) {
const schema = SCHEMA[node.local] || {};

if (
Expand Down Expand Up @@ -691,6 +693,8 @@ module.exports.xml2json = function (
current.schemaName = node.attributes.Namespace.value;
setAttributes(current.schema, node, ["Alias"], ["Namespace"]);
v2Annotations(current.schema, node, SAP_V2_SCHEMA);
if (result[node.attributes.Namespace.value])
reportError("Schema namespace collides with other schema", this);
result[node.attributes.Namespace.value] = current.schema;
annotatable.target = current.schema;
break;
Expand Down Expand Up @@ -774,6 +778,11 @@ module.exports.xml2json = function (
"Partner",
]);
}
if (current.type[node.attributes.Name.value])
reportError(
"Navigation property name collides with other property",
this
);
current.type[node.attributes.Name.value] = current.property;
annotatable.target = current.property;
break;
Expand Down Expand Up @@ -821,6 +830,8 @@ module.exports.xml2json = function (
result.$Version < "4.0" ? ["ConcurrencyMode", "FixedLength"] : []
);
v2PropertyAnnotations(current.property, node);
if (current.type[node.attributes.Name.value])
reportError("Property name collides with other property", this);
current.type[node.attributes.Name.value] = current.property;
annotatable.target = current.property;
break;
Expand All @@ -841,6 +852,11 @@ module.exports.xml2json = function (
const member = node.attributes.Name.value;
const value = Number(node.attributes.Value?.value);
addLineNumber(current.type, member);
if (current.type[member] !== undefined)
reportError(
"Enumeration member name collides with other member",
this
);
current.type[member] = Number.isNaN(value)
? current.enumMemberValue
: value;
Expand Down Expand Up @@ -945,6 +961,11 @@ module.exports.xml2json = function (
current.schemaName + "." + node.attributes.Name.value;
current.container = { $Kind: node.local };
setAttributes(current.container, node, ["Extends"]);
if (current.schema[node.attributes.Name.value])
reportError(
"Entity container name collides with other schema child",
this
);
current.schema[node.attributes.Name.value] = current.container;
annotatable.target = current.container;
break;
Expand All @@ -957,6 +978,11 @@ module.exports.xml2json = function (
"IncludeInServiceDocument",
]);
v2Annotations(current.containerChild, node, SAP_V2_ENTITY_SET);
if (current.container[node.attributes.Name.value])
reportError(
"Entity set name collides with other container child",
this
);
current.container[node.attributes.Name.value] = current.containerChild;
annotatable.target = current.containerChild;
const bindings = preV4.entitySet[node.attributes.Name.value];
Expand All @@ -978,6 +1004,11 @@ module.exports.xml2json = function (
checkAttribute(node, "Type");
current.containerChild = {};
setAttributes(current.containerChild, node, ["Type", "Nullable"]);
if (current.container[node.attributes.Name.value])
reportError(
"Singleton name collides with other container child",
this
);
current.container[node.attributes.Name.value] = current.containerChild;
annotatable.target = current.containerChild;
break;
Expand All @@ -986,6 +1017,11 @@ module.exports.xml2json = function (
checkAttribute(node, "Action");
current.containerChild = {};
setAttributes(current.containerChild, node, ["Action", "EntitySet"]);
if (current.container[node.attributes.Name.value])
reportError(
"Action import name collides with other container child",
this
);
current.container[node.attributes.Name.value] = current.containerChild;
annotatable.target = current.containerChild;
break;
Expand Down Expand Up @@ -1061,6 +1097,11 @@ module.exports.xml2json = function (
"EntitySet",
"IncludeInServiceDocument",
]);
if (current.container[node.attributes.Name.value])
reportError(
"Function import name collides with other container child",
this
);
current.container[node.attributes.Name.value] =
current.containerChild;
annotatable.target = current.containerChild;
Expand Down Expand Up @@ -1122,7 +1163,8 @@ module.exports.xml2json = function (
// fall through
case "ValueAnnotation":
case "Annotation":
if (node.local === "Annotation") checkAttribute(node, "Term");
if (["ValueAnnotation", "Annotation"].includes(node.local))
checkAttribute(node, "Term");
setAttributes(
annotation,
node,
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "odata-csdl",
"version": "0.9.1",
"version": "0.9.2",
"description": "Convert CSDL XML to CSDL JSON",
"homepage": "https://github.com/oasis-tcs/odata-csdl-schemas/blob/master/lib/README.md",
"bugs": "https://github.com/oasis-tcs/odata-csdl-schemas/issues",
Expand Down
147 changes: 147 additions & 0 deletions test/xml2json.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// V2 service with alias, mix of namespace- and alias-qualified associations/sets
// V2 service with HttpMethod=POST
// UrlRef with nested annotation
// <EnumMember> etc. outside of annotation

const assert = require("assert");
const fs = require("fs");
Expand Down Expand Up @@ -1610,4 +1611,150 @@ describe("Error cases", function () {
});
}
});

it("Name collisions: namespaces, types, (navigation) properties, enum members - last one wins", function () {
const xml = `<Edmx Version="4.0" xmlns="http://docs.oasis-open.org/odata/ns/edmx">
<DataServices>
<Schema Namespace="foo" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityType Name="ignore"/>
</Schema>
<Schema Namespace="foo" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<ComplexType Name="bar"/>
<ComplexType Name="bar"/>
<EntityContainer Name="bar"/>
<EntityType Name="bar">
<Property Name="baz" Type="Edm.String"/>
<Property Name="baz" Type="Edm.String"/>
<NavigationProperty Name="baz" Type="foo.bar"/>
</EntityType>
<EnumType Name="qux">
<Member Name="quux"/>
<Member Name="quux" Value="42"/>
</EnumType>
<EntityContainer Name="container">
<EntitySet Name="bar" EntityType="foo.qux"/>
<EntitySet Name="bar" EntityType="foo.bar"/>
<Singleton Name="bar" Type="foo.bar"/>
<ActionImport Name="bar" Action="foo.bar"/>
<FunctionImport Name="bar" Function="foo.bar"/>
</EntityContainer>
</Schema>
</DataServices>
</Edmx>`;

const messages = [];
const json = csdl.xml2json(xml, { messages });

assert.deepStrictEqual(messages, [
{
message: "Schema namespace collides with other schema",
parser: {
construct:
'<Schema Namespace="foo" xmlns="http://docs.oasis-open.org/odata/ns/edm">',
line: 6,
column: 93,
},
},
{
message: "Type name collides with other schema child",
parser: {
construct: '<ComplexType Name="bar"/>',
line: 8,
column: 48,
},
},
{
message: "Entity container name collides with other schema child",
parser: {
construct: '<EntityContainer Name="bar"/>',
line: 9,
column: 52,
},
},
{
message: "Type name collides with other schema child",
parser: {
construct: '<EntityType Name="bar">',
line: 10,
column: 46,
},
},
{
message: "Property name collides with other property",
parser: {
construct: '<Property Name="baz" Type="Edm.String"/>',
line: 12,
column: 65,
},
},
{
message: "Navigation property name collides with other property",
parser: {
construct: '<NavigationProperty Name="baz" Type="foo.bar"/>',
line: 13,
column: 72,
},
},
{
message: "Enumeration member name collides with other member",
parser: {
construct: '<Member Name="quux" Value="42"/>',
line: 17,
column: 57,
},
},
{
message: "Entity set name collides with other container child",
parser: {
construct: '<EntitySet Name="bar" EntityType="foo.bar"/>',
line: 21,
column: 69,
},
},
{
message: "Singleton name collides with other container child",
parser: {
construct: '<Singleton Name="bar" Type="foo.bar"/>',
line: 22,
column: 63,
},
},
{
message: "Action import name collides with other container child",
parser: {
construct: '<ActionImport Name="bar" Action="foo.bar"/>',
line: 23,
column: 68,
},
},
{
message: "Function import name collides with other container child",
parser: {
construct: '<FunctionImport Name="bar" Function="foo.bar"/>',
line: 24,
column: 72,
},
},
]);

assert.deepStrictEqual(json, {
$Version: "4.0",
$EntityContainer: "foo.container",
foo: {
bar: {
$Kind: "EntityType",
baz: {
$Kind: "NavigationProperty",
$Type: "foo.bar",
$Nullable: true,
},
},
qux: { $Kind: "EnumType", quux: 42 },
container: {
$Kind: "EntityContainer",
bar: { $Function: "foo.bar" },
},
},
});
});
});
Loading