forked from dropbox/dropbox-api-v2-explorer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcodeview.ts
272 lines (228 loc) · 11.4 KB
/
codeview.ts
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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
/* The functions that handle the code view part of the interface: taking the input and
representing it as an HTTP request or code to generate that request.
*/
import react = require('react');
import utils = require('./utils');
const ce = react.createElement;
const d = react.DOM;
type Renderer = (endpoint: utils.Endpoint, token: string, paramVals: utils.Dict,
headerVals: utils.Header[]) => react.ReactElement<{}>
type UploadRenderer = (endpoint: utils.Endpoint, token: string, paramVals: utils.Dict,
headerVals: utils.Header[], file: File) => react.ReactElement<{}>
/* Something which I wish I had more time to fix: in this file, "upload" and "download" have the wrong
meanings. Specifically, here, "upload" means a call with a file attached, and "download" means a
non-RPC-like call without a file (e.g. empty uploads and downloads). This might be confusing. I
originally did this because it makes more of a difference in what the code viewer looks like.
*/
// An object that handles a particular kind of code view for each kind of endpoint.
interface CodeViewer {
syntax: string;
description: string; // text of the option in the selector
/* Calls for the three kinds of endpoints: RPC-like (data neither uploaded nor downloaded),
upload-like (data uploaded as the body of the request), and download-like (no data uploaded,
but not RPC-like). If you upload with no data, it should thus be treated as download-like, which
is a bit counterintuitive.
*/
renderRPCLike: Renderer;
renderUploadLike: UploadRenderer;
renderDownloadLike: Renderer;
}
const syntaxHighlight = (syntax: string, text: react.HTMLElement): react.ReactElement<{}> =>
ce(utils.Highlight, {className: syntax}, text);
// Applies f to each element of the dict, and then appends the separator to all but the last result.
// Subsequent list elements are separated by newlines.
const joinWithNewlines = (dc: utils.Dict, f: (k: string, v: string) => string, sep: string = ','):
react.ClassicElement<{}>[] => utils.Dict._map(dc, (k: string, v: string, i: number) => {
const maybeSep = (i === Object.keys(dc).length - 1)?
"\n" : sep + "\n";
return d.span({key: "" + i}, f(k, v), maybeSep);
}
);
// the minor differences between JSON and Python's notation
const pythonStringify = (val: any): string => {
if (val === true) {
return "True";
} else if (val === false) {
return "False";
} else if (val === null || (val !== val)) {
return "None";
} else {
return JSON.stringify(val);
}
};
// Representation of a dict, or null if the passed-in dict is also null
const dictToPython = (name: string, dc: utils.Dict): react.HTMLElement => d.span(null,
name + ' = ',
(dc === null)?
'None' : d.span(null,
'{\n',
joinWithNewlines(dc, (k: string, v: any) => ' "' + k + '": ' + pythonStringify(v)),
'}'
), '\n\n'
);
// For curl calls, we need to escape single quotes, and sometimes also double quotes.
const shellEscape = (val: any, inQuotes: boolean = false): string => {
const toReturn = JSON.stringify(val).replace(/'/g, "'\\''");
if (inQuotes) return toReturn.replace(/\\/g, '\\\\').replace(/"/g, '\\\"');
else return toReturn;
};
// Generates the functions that make up the Python Requests code viewer
const RequestsCodeViewer = (): CodeViewer => {
const syntax = "python";
// common among all three parts
const preamble = (endpt: utils.Endpoint): react.HTMLElement => d.span(null,
'import requests\n', 'import json\n\n',
'url = "' + endpt.getURL() + '"\n\n'
);
const requestsTemplate = (endpt: utils.Endpoint, headers: utils.Dict,
dataReader: string|react.HTMLElement, call: string) =>
syntaxHighlight(syntax, d.span(null,
preamble(endpt), dictToPython('headers', headers), dataReader, call));
const requestsRPCLike: Renderer = (endpt, token, paramVals, headerVals) =>
requestsTemplate(endpt, utils.getHeaders(endpt, token, headerVals), dictToPython('data', paramVals),
'r = requests.post(url, headers=headers, data=json.dumps(data))');
const requestsUploadLike: UploadRenderer = (endpt, token, paramVals, headerVals, file) =>
requestsTemplate(endpt, utils.getHeaders(endpt, token, headerVals, JSON.stringify(paramVals)),
'data = open(' + JSON.stringify(file.name) + ', "rb").read()\n\n',
'r = requests.post(url, headers=headers, data=data)');
const requestsDownloadLike: Renderer = (endpt, token, paramVals, headerVals) =>
requestsTemplate(endpt, utils.getHeaders(endpt, token, headerVals, JSON.stringify(paramVals)),
'', 'r = requests.post(url, headers=headers)');
return {
syntax: syntax,
description: "Python request (requests library)",
renderRPCLike: requestsRPCLike,
renderUploadLike: requestsUploadLike,
renderDownloadLike: requestsDownloadLike
};
};
// Python's httplib library (which is also the urllib backend)
const HttplibCodeViewer = (): CodeViewer => {
const syntax = "python";
const preamble = d.span(null,
'import sys\nimport json\n',
'if (3,0) <= sys.version_info < (4,0):\n',
' import http.client as httplib\n',
'elif (2,6) <= sys.version_info < (3,0):\n',
' import httplib\n\n'
);
const httplibTemplate = (endpt: utils.Endpoint, headers: utils.Dict,
dataReader: string|react.HTMLElement, dataArg: string): react.ReactElement<{}> =>
syntaxHighlight(syntax, d.span(null,
preamble,
dictToPython('headers', headers),
dataReader,
'c = httplib.HTTPSConnection("' + endpt.getHostname() + '")\n',
'c.request("POST", "' + endpt.getPathName() + '", ' + dataArg + ', headers)\n',
'r = c.getresponse()'
)
);
const httplibRPCLike: Renderer = (endpt, token, paramVals, headerVals) =>
httplibTemplate(endpt, utils.getHeaders(endpt, token, headerVals),
dictToPython('params', paramVals), 'json.dumps(params)');
const httplibUploadLike: UploadRenderer = (endpt, token, paramVals, headerVals, file) =>
httplibTemplate(endpt, utils.getHeaders(endpt, token, headerVals, JSON.stringify(paramVals)),
'data = open(' + JSON.stringify(file.name) + ', "rb")\n\n', 'data');
const httplibDownloadLike: Renderer = (endpt, token, paramVals, headerVals) =>
httplibTemplate(endpt, utils.getHeaders(endpt, token, headerVals, JSON.stringify(paramVals)), '', '""');
return {
syntax: syntax,
description: "Python request (standard library)",
renderRPCLike: httplibRPCLike,
renderUploadLike: httplibUploadLike,
renderDownloadLike: httplibDownloadLike
};
};
const CurlCodeViewer = (): CodeViewer => {
const syntax = 'bash';
const urlArea = (endpt: utils.Endpoint) => 'curl -X POST ' + endpt.getURL() + ' \\\n';
const makeHeaders = (headers: utils.Dict): react.HTMLElement => d.span(null,
utils.Dict._map(headers, (k: string, v: string, i: number): react.HTMLElement => {
let sep = '\\\n';
if (i == Object.keys(headers).length - 1) sep = '';
return d.span({key: "" + i}, " --header '" + k + ': ' + v + "' " + sep);
})
);
// The general model of the curl call, populated with the arguments.
const curlTemplate = (endpt: utils.Endpoint, headers: utils.Dict,
data: string): react.ReactElement<{}> =>
syntaxHighlight(syntax, d.span(null, urlArea(endpt), makeHeaders(headers), data));
const curlRPCLike: Renderer = (endpt, token, paramVals, headerVals) =>
curlTemplate(endpt, utils.getHeaders(endpt, token, headerVals),
"\\\n --data '" + shellEscape(paramVals) + "'");
const curlUploadLike: UploadRenderer = (endpt, token, paramVals, headerVals, file) => {
const headers = utils.getHeaders(endpt, token, headerVals, shellEscape(paramVals, false));
return curlTemplate(endpt, headers,
"\\\n --data-binary @'" + file.name.replace(/'/g, "'\\''") + "'");
};
const curlDownloadLike: Renderer = (endpt, token, paramVals, headerVals) =>
curlTemplate(endpt, utils.getHeaders(endpt, token, headerVals, shellEscape(paramVals, false)), '');
return {
syntax: syntax,
description: "curl request",
renderRPCLike: curlRPCLike,
renderUploadLike: curlUploadLike,
renderDownloadLike: curlDownloadLike
};
};
const HTTPCodeViewer = (): CodeViewer => {
const syntax = 'http';
const httpTemplate = (endpt: utils.Endpoint, headers: utils.Dict,
body: string): react.ReactElement<{}> =>
syntaxHighlight(syntax, d.span(null,
'POST ' + endpt.getPathName() + "\n",
'Host: https://' + endpt.getHostname() + "\n",
'User-Agent: api-explorer-client\n',
utils.Dict.map(headers, (key: string, value: string) => d.span({key: key},
key + ": " + value + "\n"
)),
body
)
);
const httpRPCLike: Renderer = (endpt, token, paramVals, headerVals) => {
const body = JSON.stringify(paramVals, null, 4);
const headers = utils.getHeaders(endpt, token, headerVals);
// TODO: figure out how to determine the UTF-8 encoded length
//headers['Content-Length'] = ...
return httpTemplate(endpt, headers, "\n" + body);
};
const httpUploadLike: UploadRenderer = (endpt, token, paramVals, headerVals, file) => {
const headers = utils.getHeaders(endpt, token, headerVals, JSON.stringify(paramVals));
headers['Content-Length'] = file.size;
return httpTemplate(endpt, headers,
"\n--- (content of " + file.name + " goes here) ---");
};
const httpDownloadLike: Renderer = (endpt, token, paramVals, headerVals) => {
const headers = utils.getHeaders(endpt, token, headerVals, JSON.stringify(paramVals));
return httpTemplate(endpt, headers, '');
};
return {
syntax: syntax,
description: 'HTTP request',
renderRPCLike: httpRPCLike,
renderUploadLike: httpUploadLike,
renderDownloadLike: httpDownloadLike
}
};
export const formats: utils.Dict = {
'curl': CurlCodeViewer(),
'requests': RequestsCodeViewer(),
'httplib': HttplibCodeViewer(),
'http': HTTPCodeViewer()
};
export const getSelector = (onChange: (e: react.FormEvent) => void): react.HTMLElement => d.select(
{onChange: onChange},
utils.Dict.map(formats, (key: string, cv: CodeViewer) =>
d.option({key: key, value: key}, cv.description))
);
export const render = (cv: CodeViewer, endpt: utils.Endpoint,
token: string, paramVals: utils.Dict,
headerVals: utils.Header[], file: File): react.ReactElement<{}> => {
if (endpt.getEndpointKind() === utils.EndpointKind.RPCLike) {
return cv.renderRPCLike(endpt, token, paramVals, headerVals);
} else if (file !== null) {
return cv.renderUploadLike(endpt, token, paramVals, headerVals, file);
} else {
return cv.renderDownloadLike(endpt, token, paramVals, headerVals);
}
};