diff --git a/lib/connector.js b/lib/connector.js index 09d20ea..e5ed811 100644 --- a/lib/connector.js +++ b/lib/connector.js @@ -135,8 +135,40 @@ Connector.prototype.getConnectorSpecificSettings = function(modelName) { * @returns {Object} Property definition */ Connector.prototype.getPropertyDefinition = function(modelName, propName) { - var model = this.getModelDefinition(modelName); - return model && model.properties[propName]; + const model = this.getModelDefinition(modelName); + return Connector.getNestedPropertyDefinition( + model.model.definition, + propName.split('.') + ); +}; + +/** + * Helper function to get nested property definition + * @param {Object} definition Model name + * @param {Array} propPath + * @returns {Object} Property definition + */ +Connector.getNestedPropertyDefinition = function(definition, propPath) { + const properties = definition.properties || {}; + const prop = properties[propPath[0]]; + const isPropUndefined = typeof prop === 'undefined'; + const isArray = !isPropUndefined && Array.isArray(prop.type); + const isFunction = !isPropUndefined && !isArray && typeof prop.type === 'function'; + + if (isPropUndefined || (propPath.length > 1 && (isArray && prop.type.length === 0))) { + throw new Error(g.f('Invalid property path')); + } + + if (propPath.length === 1) return prop; + + const nextDefinition = + (isArray && prop.type[0].definition) || + (isFunction && prop.type.definition); + + return Connector.getNestedPropertyDefinition( + nextDefinition, + propPath.slice(1) + ); }; /** diff --git a/test/connector.test.js b/test/connector.test.js new file mode 100644 index 0000000..cc5985c --- /dev/null +++ b/test/connector.test.js @@ -0,0 +1,107 @@ +// Copyright IBM Corp. 2016. All Rights Reserved. +// Node module: loopback-connector +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +'use strict'; + +const expect = require('chai').expect; +const {ModelBuilder} = require('loopback-datasource-juggler'); +const Connector = require('../lib/connector'); + +describe('Connector', () => { + describe('getPropertyDefinition()', () => { + let connector, builder; + + beforeEach(() => { + connector = new Connector('MyConnector'); + builder = new ModelBuilder(); + + const MyModel = builder.define('MyModel', { + firstname: String, + phoneList: [ + { + number: Number, + label: { + title: String, + }, + }, + ], + address: { + line1: String, + }, + someProp: { + innerArray: [ + { + date: Date, + }, + ], + }, + }); + + connector.define({ + model: MyModel, + }); + }); + + it('supports retrieving first level properties definitions', () => { + const propDefinition1 = connector.getPropertyDefinition( + 'MyModel', + 'phoneList' + ); + + expect(propDefinition1.type).to.be.an('array'); + + const propDefinition2 = connector.getPropertyDefinition( + 'MyModel', + 'firstname' + ); + + expect(propDefinition2.type).to.be.equal(String); + }); + + it('supports first level nested array property definitions', () => { + const propDefinition = connector.getPropertyDefinition( + 'MyModel', + 'phoneList.number' + ); + + expect(propDefinition.type).to.equal(Number); + }); + + it('supports second level nested array property definitions', () => { + const propDefinition = connector.getPropertyDefinition( + 'MyModel', + 'phoneList.label.title' + ); + + expect(propDefinition.type).to.equal(String); + }); + + it('supports nested property definitions on objects', () => { + const propDefinition = connector.getPropertyDefinition( + 'MyModel', + 'address.line1' + ); + + expect(propDefinition.type).to.equal(String); + }); + + it('supports nested property definitions on array within object', () => { + const propDefinition = connector.getPropertyDefinition( + 'MyModel', + 'someProp.innerArray.date' + ); + + expect(propDefinition.type).to.equal(Date); + }); + + it('should fail for non-existing properties', () => { + expect(() => + connector.getPropertyDefinition( + 'MyModel', + 'non.existing.property' + )).to.throw(/Invalid property path/); + }); + }); +});