[Composition] Composition api improvements (#1332). Fixes #1322 and Fixes #1253

* [Composition] provide ids, sync via mutation

Composition provides ids, and we sync things via mutation.  This
simplifies the composition provider interface some, and also
fixes some issues with the previous default composition provider
related to #1322
fixes #1253

* [Style] Fix style, update jsdoc

Fix style, update jsdoc, clean up composition api changes for

Fixes #1322

* [Style] Tidy and JSDoc

* [Composition] Utilize new composition API

Ensures that composition provided by new API works in old API.

Some functionality is not present in both places, but for the
time being this is sufficient.

https://github.com/nasa/openmct/pull/1332

* [Utils] add tests, fix bugs

Add tests to objectUtils to ensure correctness.  This caught a bug
where character escapes were not properly applied or removed.  As
a result, any missing object that contained a colon in it's key
would cause an infinite loop and cause the application to crash.

Bug discovered in VISTA integration.

* [Style] Fix style

* [Roots] Depend on new api for ROOT

Depend on new API for ROOT model, ensuring consistency when
fetching ROOT model.

* [Style] Remove commented code
This commit is contained in:
Pete Richards
2016-11-30 12:00:01 -08:00
committed by Andrew Henry
parent 79b4f9a0f4
commit 73b3ae7264
13 changed files with 515 additions and 393 deletions

View File

@ -32,6 +32,10 @@ define([
return this.rootRegistry.getRoots()
.then(function (roots) {
return {
identifier: {
key: "ROOT",
namespace: ""
},
name: 'The root object',
type: 'root',
composition: roots

View File

@ -26,31 +26,50 @@ define([
) {
// take a key string and turn it into a key object
// 'scratch:root' ==> {namespace: 'scratch', identifier: 'root'}
var parseKeyString = function (identifier) {
if (typeof identifier === 'object') {
return identifier;
/**
* Utility for checking if a thing is an Open MCT Identifier.
* @private
*/
function isIdentifier(thing) {
return typeof thing === 'object' &&
thing.hasOwnProperty('key') &&
thing.hasOwnProperty('namespace');
}
/**
* Utility for checking if a thing is a key string. Not perfect.
* @private
*/
function isKeyString(thing) {
return typeof thing === 'string';
}
/**
* Convert a keyString into an Open MCT Identifier, ex:
* 'scratch:root' ==> {namespace: 'scratch', key: 'root'}
*
* Idempotent.
*
* @param keyString
* @returns identifier
*/
function parseKeyString(keyString) {
if (isIdentifier(keyString)) {
return keyString;
}
var namespace = '',
key = identifier;
for (var i = 0, escaped = false; i < key.length; i++) {
if (escaped) {
escaped = false;
namespace += key[i];
} else {
if (identifier[i] === "\\") {
escaped = true;
} else if (identifier[i] === ":") {
// namespace = key.slice(0, i);
key = identifier.slice(i + 1);
break;
}
namespace += identifier[i];
key = keyString;
for (var i = 0; i < key.length; i++) {
if (key[i] === "\\" && key[i + 1] === ":") {
i++; // skip escape character.
} else if (key[i] === ":") {
key = key.slice(i + 1);
break;
}
namespace += key[i];
}
if (identifier === namespace) {
if (keyString === namespace) {
namespace = '';
}
@ -58,52 +77,95 @@ define([
namespace: namespace,
key: key
};
};
}
// take a key and turn it into a key string
// {namespace: 'scratch', identifier: 'root'} ==> 'scratch:root'
var makeKeyString = function (identifier) {
if (typeof identifier === 'string') {
/**
* Convert an Open MCT Identifier into a keyString, ex:
* {namespace: 'scratch', key: 'root'} ==> 'scratch:root'
*
* Idempotent
*
* @param identifier
* @returns keyString
*/
function makeKeyString(identifier) {
if (isKeyString(identifier)) {
return identifier;
}
if (!identifier.namespace) {
return identifier.key;
}
return [
identifier.namespace.replace(':', '\\:'),
identifier.key.replace(':', '\\:')
identifier.namespace.replace(/\:/g, '\\:'),
identifier.key
].join(':');
};
}
// Converts composition to use key strings instead of keys
var toOldFormat = function (model) {
/**
* Convert a new domain object into an old format model, removing the
* identifier and converting the composition array from Open MCT Identifiers
* to old format keyStrings.
*
* @param domainObject
* @returns oldFormatModel
*/
function toOldFormat(model) {
model = JSON.parse(JSON.stringify(model));
delete model.identifier;
if (model.composition) {
model.composition = model.composition.map(makeKeyString);
}
return model;
};
}
// converts composition to use keys instead of key strings
var toNewFormat = function (model, identifier) {
/**
* Convert an old format domain object model into a new format domain
* object. Adds an identifier using the provided keyString, and converts
* the composition array to utilize Open MCT Identifiers.
*
* @param model
* @param keyString
* @returns domainObject
*/
function toNewFormat(model, keyString) {
model = JSON.parse(JSON.stringify(model));
model.identifier = parseKeyString(identifier);
model.identifier = parseKeyString(keyString);
if (model.composition) {
model.composition = model.composition.map(parseKeyString);
}
return model;
};
}
var equals = function (a, b) {
return makeKeyString(a.key) === makeKeyString(b.key);
};
/**
* Compare two Open MCT Identifiers, returning true if they are equal.
*
* @param identifier
* @param otherIdentifier
* @returns Boolean true if identifiers are equal.
*/
function identifierEquals(a, b) {
return a.key === b.key && a.namespace === b.namespace;
}
/**
* Compare two domain objects, return true if they're the same object.
* Equality is determined by identifier.
*
* @param domainObject
* @param otherDomainOBject
* @returns Boolean true if objects are equal.
*/
function objectEquals(a, b) {
return identifierEquals(a.identifier, b.identifier);
}
return {
toOldFormat: toOldFormat,
toNewFormat: toNewFormat,
makeKeyString: makeKeyString,
parseKeyString: parseKeyString,
equals: equals
equals: objectEquals,
identifierEquals: identifierEquals
};
});

View File

@ -48,6 +48,10 @@ define([
rootObjectProvider.get()
.then(function (root) {
expect(root).toEqual({
identifier: {
key: "ROOT",
namespace: ""
},
name: 'The root object',
type: 'root',
composition: ['some root']

View File

@ -0,0 +1,153 @@
define([
'../object-utils'
], function (
objectUtils
) {
describe('objectUtils', function () {
describe('keyString util', function () {
var EXPECTATIONS = {
'ROOT': {
namespace: '',
key: 'ROOT'
},
'mine': {
namespace: '',
key: 'mine'
},
'extended:something:with:colons': {
key: 'something:with:colons',
namespace: 'extended'
},
'https\\://some/url:resourceId': {
key: 'resourceId',
namespace: 'https://some/url'
},
'scratch:root': {
namespace: 'scratch',
key: 'root'
},
'thingy\\:thing:abc123': {
namespace: 'thingy:thing',
key: 'abc123'
}
};
Object.keys(EXPECTATIONS).forEach(function (keyString) {
it('parses "' + keyString + '".', function () {
expect(objectUtils.parseKeyString(keyString))
.toEqual(EXPECTATIONS[keyString]);
});
it('parses and re-encodes "' + keyString + '"', function () {
var identifier = objectUtils.parseKeyString(keyString);
expect(objectUtils.makeKeyString(identifier))
.toEqual(keyString);
});
it('is idempotent for "' + keyString + '".', function () {
var identifier = objectUtils.parseKeyString(keyString);
var again = objectUtils.parseKeyString(identifier);
expect(identifier).toEqual(again);
again = objectUtils.parseKeyString(again);
again = objectUtils.parseKeyString(again);
expect(identifier).toEqual(again);
var againKeyString = objectUtils.makeKeyString(again);
expect(againKeyString).toEqual(keyString);
againKeyString = objectUtils.makeKeyString(againKeyString);
againKeyString = objectUtils.makeKeyString(againKeyString);
againKeyString = objectUtils.makeKeyString(againKeyString);
expect(againKeyString).toEqual(keyString);
});
});
});
describe('old object conversions', function () {
it('translate ids', function () {
expect(objectUtils.toNewFormat({
prop: 'someValue'
}, 'objId'))
.toEqual({
prop: 'someValue',
identifier: {
namespace: '',
key: 'objId'
}
});
});
it('translates composition', function () {
expect(objectUtils.toNewFormat({
prop: 'someValue',
composition: [
'anotherObjectId',
'scratch:anotherObjectId'
]
}, 'objId'))
.toEqual({
prop: 'someValue',
composition: [
{
namespace: '',
key: 'anotherObjectId'
},
{
namespace: 'scratch',
key: 'anotherObjectId'
}
],
identifier: {
namespace: '',
key: 'objId'
}
});
});
});
describe('new object conversions', function () {
it('removes ids', function () {
expect(objectUtils.toOldFormat({
prop: 'someValue',
identifier: {
namespace: '',
key: 'objId'
}
}))
.toEqual({
prop: 'someValue'
});
});
it('translates composition', function () {
expect(objectUtils.toOldFormat({
prop: 'someValue',
composition: [
{
namespace: '',
key: 'anotherObjectId'
},
{
namespace: 'scratch',
key: 'anotherObjectId'
}
],
identifier: {
namespace: '',
key: 'objId'
}
}))
.toEqual({
prop: 'someValue',
composition: [
'anotherObjectId',
'scratch:anotherObjectId'
]
});
});
});
});
});