diff --git a/package.json b/package.json index ef214277d0..eb07804f4c 100644 --- a/package.json +++ b/package.json @@ -138,6 +138,7 @@ "build-react-native-dist": "webpack --config dist-tools/webpack.config.rn.js", "build-react-native": "npm -s run-script build-react-native-deps && npm -s run-script build-react-native-core && npm -s run-script build-react-native-dist", "react-native-test": "npm -s run-script build-react-native && rake reactnative:test && karma start", - "region-check": "node ./scripts/region-checker/index.js" + "region-check": "node ./scripts/region-checker/index.js", + "test-remove-event-stream": "mocha scripts/lib/remove-event-stream-ops.spec.js" } } \ No newline at end of file diff --git a/scripts/lib/foo-2018-03-30.normal.json b/scripts/lib/foo-2018-03-30.normal.json new file mode 100644 index 0000000000..cb3750ccc0 --- /dev/null +++ b/scripts/lib/foo-2018-03-30.normal.json @@ -0,0 +1,104 @@ +{ + "version": "2.0", + "metadata": { + "apiVersion": "2018-03-30", + "endpointPrefix": "foo", + "protocol": "rest-json", + "serviceId": "Foo", + "uid": "foo-2018-03-30" + }, + "operations": { + "BarOperation": { + "name": "BarOperation", + "http": { + "method": "GET", + "requireUri": "/" + }, + "input": { + "shape": "BarOperationInput" + }, + "output": { + "shape": "BarOperationOutput" + } + }, + "EventStreamOnInputOperation": { + "name": "EventStreamOnInputOperation", + "http": { + "method": "GET", + "requireUri": "/" + }, + "input": { + "shape": "EventStreamStructure" + } + }, + "EventStreamOnInputPayloadOperation": { + "name": "EventStreamOnInputPayloadOperation", + "http": { + "method": "GET", + "requireUri": "/" + }, + "input": { + "shape": "EventStreamPayload" + } + }, + "EventStreamOnOutputOperation": { + "name": "EventStreamOnOutputOperation", + "http": { + "method": "GET", + "requireUri": "/" + }, + "output": { + "shape": "EventStreamStructure" + } + }, + "EventStreamOnOutputPayloadOperation": { + "name": "EventStreamOnOutputPayloadOperation", + "http": { + "method": "GET", + "requireUri": "/" + }, + "output": { + "shape": "EventStreamPayload" + } + } + }, + "shapes": { + "BarOperationInput": { + "type": "structure", + "members": { + "String": { + "shape": "StringShape" + } + } + }, + "BarOperationOutput": { + "type": "structure", + "members": { + "String": { + "shape": "StringShape" + } + } + }, + "EventStreamPayload": { + "type": "structure", + "members": { + "Payload": { + "shape": "EventStreamStructure" + }, + "payload": "Payload" + } + }, + "EventStreamStructure": { + "type": "structure", + "members": { + "String": { + "shape": "StringShape" + } + }, + "eventstream": true + }, + "StringShape": { + "type": "string" + } + } +} \ No newline at end of file diff --git a/scripts/lib/remove-event-stream-ops.js b/scripts/lib/remove-event-stream-ops.js new file mode 100644 index 0000000000..aa87a05137 --- /dev/null +++ b/scripts/lib/remove-event-stream-ops.js @@ -0,0 +1,57 @@ +/** + * Removes operations from the model if they require event streams. + * Specifically looks at input and output shapes. + * @param {Object} model - JSON parsed API model (*.normal.json) + */ +function removeEventStreamOperations(model) { + var modifiedModel = false; + // loop over all operations + var operations = model.operations; + var operationNames = Object.keys(operations); + for (var i = 0; i < operationNames.length; i++) { + var operationName = operationNames[i]; + var operation = operations[operationName]; + // check input and output shapes + var inputShapeName = operation.input && operation.input.shape; + var outputShapeName = operation.output && operation.output.shape; + + var requiresEventStream = false; + if (inputShapeName && hasEventStream(model.shapes[inputShapeName], model)) { + requiresEventStream = true; + } + if (outputShapeName && hasEventStream(model.shapes[outputShapeName], model)) { + requiresEventStream = true; + } + + if (requiresEventStream) { + modifiedModel = true; + // remove the operation from the model + console.log('Removing ' + operationName + ' because it depends on event streams.'); + delete model.operations[operationName]; + } + } + return modifiedModel; +} + +function hasEventStream(shape, model) { + if (shape.eventstream) { + return true; + } else { + // check each member shape + var memberNames = Object.keys(shape.members); + for (var i = 0; i < memberNames.length; i++) { + var member = shape.members[memberNames[i]]; + if (member.eventstream) { + return true; + } + var memberShape = model.shapes[member.shape]; + if (memberShape.eventstream) { + return true; + } + } + } +} + +module.exports = { + removeEventStreamOperations: removeEventStreamOperations +}; \ No newline at end of file diff --git a/scripts/lib/remove-event-stream-ops.spec.js b/scripts/lib/remove-event-stream-ops.spec.js new file mode 100644 index 0000000000..1b71a06330 --- /dev/null +++ b/scripts/lib/remove-event-stream-ops.spec.js @@ -0,0 +1,93 @@ +var expect = require('chai').expect; +var removeEventStreamOperations = require('./remove-event-stream-ops').removeEventStreamOperations; +var fooModel = require('./foo-2018-03-30.normal.json'); + +describe('removeEventStreamOperations', function() { + describe('removes operations when eventstream', function() { + it('is on the input shape shape', function() { + var mockModel = deepCopyObject(fooModel); + expect(typeof mockModel.operations['EventStreamOnInputOperation']).to.equal('object'); + removeEventStreamOperations(mockModel); + expect(typeof mockModel.operations['EventStreamOnInputOperation']).to.equal('undefined'); + }); + + it('is on the input shape payload shape', function() { + var mockModel = deepCopyObject(fooModel); + expect(typeof mockModel.operations['EventStreamOnInputPayloadOperation']).to.equal('object'); + removeEventStreamOperations(mockModel); + expect(typeof mockModel.operations['EventStreamOnInputPayloadOperation']).to.equal('undefined'); + }); + + it('is on the output shape ', function() { + var mockModel = deepCopyObject(fooModel); + expect(typeof mockModel.operations['EventStreamOnOutputOperation']).to.equal('object'); + removeEventStreamOperations(mockModel); + expect(typeof mockModel.operations['EventStreamOnOutputOperation']).to.equal('undefined'); + }); + + it('is on the output shape payload shape', function() { + var mockModel = deepCopyObject(fooModel); + expect(typeof mockModel.operations['EventStreamOnOutputPayloadOperation']).to.equal('object'); + removeEventStreamOperations(mockModel); + expect(typeof mockModel.operations['EventStreamOnOutputPayloadOperation']).to.equal('undefined'); + }); + }); + + describe('does not remove operations', function() { + it('when eventstream is not present on input or output shapes', function() { + var mockModel = deepCopyObject(fooModel); + expect(typeof mockModel.operations['BarOperation']).to.equal('object'); + removeEventStreamOperations(mockModel); + expect(typeof mockModel.operations['BarOperation']).to.equal('object'); + }); + }); + + it('returns true when an operation is removed', function() { + var mockModel = deepCopyObject(fooModel); + expect(typeof mockModel.operations['EventStreamOnOutputPayloadOperation']).to.equal('object'); + var didRemove = removeEventStreamOperations(mockModel); + expect(typeof mockModel.operations['EventStreamOnOutputPayloadOperation']).to.equal('undefined'); + expect(didRemove).to.equal(true); + }); + + it('returns false when no operations are removed', function() { + var mockModel = deepCopyObject(fooModel); + // delete operations we know will be removed + var operationsToRemove = [ + 'EventStreamOnInputOperation', + 'EventStreamOnInputPayloadOperation', + 'EventStreamOnOutputOperation', + 'EventStreamOnOutputPayloadOperation' + ]; + for (var i = 0; i < operationsToRemove.length; i++) { + delete mockModel.operations[operationsToRemove[i]]; + } + expect(typeof mockModel.operations['BarOperation']).to.equal('object'); + var didRemove = removeEventStreamOperations(mockModel); + expect(typeof mockModel.operations['BarOperation']).to.equal('object'); + expect(didRemove).to.equal(false); + }); +}); + +function deepCopyObject(original) { + if (typeof original !== 'object' || original === null) { + return original; + } + var newObject = {}; + var keys = Object.keys(original); + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var value = original[key]; + if (Array.isArray(value)) { + newObject[key] = []; + for (var j = 0; j < value.length; j++) { + newObject[key].push(deepCopyObject(value[j])); + } + } else if (typeof value === 'object' && value !== null) { + newObject[key] = deepCopyObject(value) + } else { + newObject[key] = value; + } + } + return newObject; +} \ No newline at end of file diff --git a/scripts/translate-api b/scripts/translate-api index 015b31237f..3f9453a78f 100755 --- a/scripts/translate-api +++ b/scripts/translate-api @@ -2,6 +2,7 @@ var fs = require('fs'); var Translator = require('./lib/translator'); +var removeEventStreamOperations = require('./lib/remove-event-stream-ops').removeEventStreamOperations; var util = require('util'); var basePath = __dirname + '/../apis/'; @@ -12,6 +13,11 @@ paths.forEach(function (path) { if (path.match(new RegExp(modelName + ".+\\.normal\\.json$"))) { var opath = path.replace(/\.normal\.json$/, '.min.json'); var data = JSON.parse(fs.readFileSync(basePath + path).toString()); + var didModify = removeEventStreamOperations(data); + if (didModify) { + // original model modified, replace existing normal.json so docs/ts definitions are accurate + fs.writeFileSync(basePath + path, JSON.stringify(data, null, ' ')); + } var translated = new Translator(data, {documentation: false}); var json = JSON.stringify(translated, null, ' '); fs.writeFileSync(basePath + opath, json);