-
Notifications
You must be signed in to change notification settings - Fork 27
/
Copy pathapi-browser.js
138 lines (113 loc) · 4.11 KB
/
api-browser.js
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
const fs = require('fs');
const { camelCase } = require('../naming-conventions');
let apiSpec; // Lazy-loaded below.
const OPERATIONS = ['post', 'get', 'delete'];
class TwilioApiBrowser {
constructor(spec) {
spec = spec || this.loadApiSpecFromDisk();
spec = this.mergeVersions(spec);
this.domains = this.loadDomains(spec);
}
mergeVersions(spec) {
// merge the domain_versions into a single domain
const mergedSpec = {};
for (const domainNameWithVersion in spec) {
if (spec.hasOwnProperty(domainNameWithVersion)) {
const domainName = domainNameWithVersion.split('_')[0];
if (domainName in mergedSpec) {
const existing = mergedSpec[domainName];
const current = spec[domainNameWithVersion];
Object.assign(existing.components.schemas, current.components.schemas);
Object.assign(existing.paths, current.paths);
mergedSpec[domainName] = existing;
} else {
mergedSpec[domainName] = spec[domainNameWithVersion];
}
}
}
return mergedSpec;
}
loadApiSpecFromDisk() {
if (!apiSpec) {
const specPattern = /twilio_(.+)\.json/;
const specNameIndex = 1;
apiSpec = fs
.readdirSync(__dirname)
.filter((filename) => filename.match(specPattern))
.map((filename) => {
const domainName = filename.match(specPattern)[specNameIndex];
return { [domainName]: require(`./${filename}`) };
});
apiSpec = Object.assign({}, ...apiSpec);
}
return apiSpec;
}
updateTwilioVendorExtensionProperty(input) {
Object.entries(input).forEach(([key, value]) => {
if (key === 'x-twilio') {
Object.entries(value).forEach(([subKey, subValue]) => {
input[subKey] = subValue;
});
delete input[key];
}
});
}
loadDomains(obj) {
// Clone the spec since we'll be modifying it.
const domains = JSON.parse(JSON.stringify(obj));
Object.values(domains).forEach((spec) => {
Object.entries(spec.paths).forEach((entry) => {
const key = entry[0];
const path = entry[1];
if (key === '/healthcheck') return;
// Naive assumption: The Twilio APIs only have a single server.
if (path.servers === undefined) path.server = spec.servers[0].url;
else path.server = path.servers[0].url;
delete path.servers;
path.operations = {};
if (path.description === undefined) path.description = '';
path.description = path.description.replace(/(\r\n|\n|\r)/gm, ' ');
// Move the operations into an operations object.
OPERATIONS.forEach((operationName) => {
if (operationName in path) {
const operation = path[operationName];
this.updateTwilioVendorExtensionProperty(operation);
path.operations[operationName] = operation;
delete path[operationName];
/*
* Convert all the request body properties to query parameters for
* simpler parsing downstream.
*/
const parameters = this.requestPropertiesToParameters(operation.requestBody);
if (parameters.length > 0) {
operation.parameters = operation.parameters ? operation.parameters.concat(parameters) : parameters;
}
}
});
// Lift the Twilio vendor extension properties.
this.updateTwilioVendorExtensionProperty(path);
});
});
return domains;
}
requestPropertiesToParameters(requestBody) {
const parameters = [];
const content = (requestBody || {}).content || {};
Object.values(content).forEach((type) => {
const typeSchema = type.schema || {};
const properties = typeSchema.properties || {};
const required = typeSchema.required || [];
Object.entries(properties).forEach(([name, schema]) => {
parameters.push({
name,
schema,
in: 'query',
required: required.includes(name),
description: schema.description,
});
});
});
return parameters;
}
}
module.exports = TwilioApiBrowser;