translator.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. /* A couple of utility methods */
  2. function each(obj, iter) {
  3. for (var key in obj) {
  4. if (obj.hasOwnProperty(key)) iter(key, obj[key]);
  5. }
  6. }
  7. function nextString(str) {
  8. return 'S' + (parseInt(str.substr(1), 36) + 1).toString(36);
  9. }
  10. /* End utility methods */
  11. function Translator(api, options) {
  12. var origLength = JSON.stringify(api, null, 2).length;
  13. var debugInfo = {flattened: {}, pruned: {}};
  14. var shapeName = 'S0';
  15. var shapeNameMap = {};
  16. var visitedShapes = {};
  17. function logResults() {
  18. console.log('** Generated', api.metadata.endpointPrefix + '-' +
  19. api.metadata.apiVersion +'.min.json' +
  20. (process.env.DEBUG ? ':' : ''));
  21. if (process.env.DEBUG) {
  22. var pruned = Object.keys(debugInfo.pruned);
  23. var flattened = Object.keys(debugInfo.flattened);
  24. var newLength = JSON.stringify(api, null, 2).length;
  25. console.log('- Pruned Shapes:', pruned.length);
  26. console.log('- Flattened Shapes:', flattened.length);
  27. console.log('- Remaining Shapes:', Object.keys(api.shapes).length);
  28. console.log('- Original Size:', origLength / 1024.0, 'kb');
  29. console.log('- Minified Size:', newLength / 1024.0, 'kb');
  30. console.log('- Size Saving:', (origLength - newLength) / 1024.0, 'kb');
  31. console.log('');
  32. }
  33. }
  34. function deleteTraits(obj) {
  35. if (!options.documentation) {
  36. delete obj.documentation;
  37. delete obj.documentationUrl;
  38. delete obj.errors;
  39. delete obj.min;
  40. delete obj.max;
  41. delete obj.pattern;
  42. delete obj['enum'];
  43. delete obj.box;
  44. }
  45. }
  46. function trackShapeDeclaration(ref) {
  47. if (ref.shape && !shapeNameMap[ref.shape]) {
  48. // found a shape declaration we have not yet visited.
  49. // assign a new generated name in the shapeNameMap & visit it
  50. var oldShapeName = ref.shape;
  51. ref.shape = shapeName = nextString(shapeName);
  52. visitedShapes[shapeName] = api.shapes[oldShapeName];
  53. shapeNameMap[oldShapeName] = {name: shapeName, refs: [ref]};
  54. traverseShapeRef(api.shapes[oldShapeName]);
  55. } else if (ref.shape && shapeNameMap[ref.shape]) {
  56. // we visited this shape before. keep track of this ref and rename
  57. // the referenced declaration to the generated name
  58. var map = shapeNameMap[ref.shape];
  59. map.refs.push(ref);
  60. ref.shape = map.name;
  61. }
  62. }
  63. function pruneShapes() {
  64. // prune shapes visited only once or only have type specifiers
  65. each(shapeNameMap, function(name, map) {
  66. if (Object.keys(visitedShapes[map.name]).join() === 'type' &&
  67. ['structure','map','list'].indexOf(visitedShapes[map.name].type) < 0) {
  68. // flatten out the shape (only a scalar type property is on the shape)
  69. for (var i = 0; i < map.refs.length; i++) {
  70. var ref = map.refs[i];
  71. debugInfo.flattened[name] = true;
  72. delete ref.shape;
  73. ref.type = visitedShapes[map.name].type;
  74. // string type is default, don't need to specify this
  75. if (ref.type === 'string') delete ref.type;
  76. }
  77. // we flattened all refs, we can prune the shape too
  78. delete visitedShapes[map.name];
  79. debugInfo.pruned[name] = true;
  80. } else if (map.refs.length === 1) { // only visited once
  81. // merge shape data onto ref
  82. var shape = visitedShapes[map.name];
  83. for (var i = 0; i < map.refs.length; i++) {
  84. delete map.refs[i].shape;
  85. for (var prop in shape) {
  86. if (shape.hasOwnProperty(prop)) {
  87. //Translator prefers timestampFormat trait in members rather than in shape
  88. if (map.refs[i].hasOwnProperty(prop) && ['timestampFormat'].indexOf(prop) >= 0) {
  89. continue;
  90. }
  91. map.refs[i][prop] = shape[prop];
  92. }
  93. }
  94. }
  95. // delete the visited shape
  96. delete visitedShapes[map.name];
  97. debugInfo.pruned[name] = true;
  98. }
  99. });
  100. }
  101. function traverseShapeRef(ref) {
  102. if (!ref) return;
  103. deleteTraits(ref);
  104. traverseShapeRef(ref.key); // for maps
  105. traverseShapeRef(ref.value); // for maps
  106. traverseShapeRef(ref.member); // for lists
  107. // for structures
  108. each(ref.members || {}, function(key, value) { traverseShapeRef(value); });
  109. // resolve shape declarations
  110. trackShapeDeclaration(ref);
  111. }
  112. function traverseOperation(op) {
  113. deleteTraits(op);
  114. delete op.name;
  115. if (op.http) {
  116. if (op.http.method === 'POST') delete op.http.method;
  117. if (op.http.requestUri === '/') delete op.http.requestUri;
  118. if (Object.keys(op.http).length === 0) delete op.http;
  119. }
  120. traverseShapeRef(op.input);
  121. traverseShapeRef(op.output);
  122. }
  123. function traverseApi() {
  124. deleteTraits(api);
  125. each(api.operations, function(name, op) { traverseOperation(op); });
  126. api.shapes = visitedShapes;
  127. }
  128. traverseApi();
  129. pruneShapes();
  130. logResults();
  131. return api;
  132. }
  133. module.exports = Translator;