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