-
Notifications
You must be signed in to change notification settings - Fork 0
/
Convert-cURL-to-Proc-HTTP.sas
321 lines (279 loc) · 9.59 KB
/
Convert-cURL-to-Proc-HTTP.sas
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
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
/***
Created by David Weik
Please check out the accompanying blog post https://www.davidweik.org/blogfrom-curl-to-proc-http
*/
* cURLString contains the full curl command;
%let cURLString = curl 'http://httpbin.org/post' --data-raw '{'name': 'Postman Example'}' --header 'First-Header: Hello World' --header 'Second-Header: Hello David';
%let cURLString = %sysfunc(translate(%superq(cURLString),%str(%'),%str(%")));
* Add SAS authentication;
* Set to 1 if you want to make a call to Viya from the same Viya Host;
%let sasAuthentication = 0;
* Output Options;
* 0 = ignore, 1 = print to log, 2 = save to table, 3 = create json lib;
%let outputOption = 3;
* Name of the output table - only applies if outputOption = 2;
%let tableName = work.http_response;
* Split up the cURL command into its parts;
data work._curlString;
length curlComponent $32000. tag $32. contentType $10. skipFlip 8. proxyURL 8. headerFlip 8. dataFlip 8.;
label curlComponent = 'Stores the value from the original curl command'
tag = 'Stores what type of information is stored in the curlComponent'
contentType = 'Specifies if something is a procedure options or statement'
skipFlip = 'If 1 remove from output'
proxyURL = 'If 1 then the next URL is a proxy URL'
headerFlip = 'If 1 then collect header'
dataFlip = 'If 1 then collect data';
* Setup special case handling defaults;
skipFlip = 0;
proxyURL = 0;
headerFlip = 0;
dataFlip = 0;
* Count the arguments of the cURL command;
arguments = countw("%superq(cURLString)", ' ');
do i = 1 to arguments;
* Reset Tag value;
tag = '';
* Content Type - default is option as most cURL options are procedural options;
contentType = 'option';
curlComponent = compress(scan("%superq(cURLString)", i, ' '));
* Tag assignment;
if curlComponent in ('-G', '--get', 'GET', 'POST', 'HEAD', 'PUT', 'DELETE') then do;
tag = 'method';
headerFlip = 0;
dataFlip = 0;
end;
else if curlComponent in ('-F', '--form', '--data-raw', '--data-urlencode', '--data-binary', '-T', '--upload-file', '-d', '--data') then do;
dataFlip = 1;
headerFlip = 0;
end;
else if prxmatch('/https{0,1}:\/\//', curlComponent) ne 0 then do;
tag = 'url';
dataFlip = 0;
headerFlip = 0;
end;
else if curlComponent in ('-H', '--header') then do;
headerFlip = 1;
dataFlip = 0;
end;
else if prxmatch('/^--{0,1}/', curlComponent) ne 0 then do;
tag = 'options';
dataFlip = 0;
headerFlip = 0;
end;
else tag = 'value';
* Proxy URL Check;
if proxyURL = 1 and tag = 'url' then do;
tag = 'proxy';
proxyURL = 0;
end;
* Check for header;
if headerFlip = 1 and tag ne '' then tag = 'header';
* Check for data;
if dataFlip = 1 and tag ne '' then tag = 'data';
* Data Warning to remind user of changing to filename;
if curlComponent in ('--data-binary', '-T', '--upload-file', '-d', '--data') then do;
put 'WARNING: You are sending data that might use a file as an input.';
put 'WARNING: In SAS you need to change this to a filename and use the file reference in the in option.';
end;
* Content Type Check;
if curlComponent in ('-v', '--verbose', '--trace') or tag = 'header' then contentType = 'statement';
* Set skip flip;
if curlComponent in ('--trace') then skipFlip = 1;
* Remove meaningless content;
if curlComponent = 'curl' then;
else if curlComponent = '\' then;
else if curlComponent in ('-X', '--request') then;
else if curlComponent in ('-x', '--proxy') then proxyURL = 1;
else if skipFlip = 1 and curlComponent not in ('--trace') then skipFlip = 0;
else if headerFlip = 1 and tag eq '' then;
else if dataFlip = 1 and tag eq '' then;
else output;
end;
drop arguments skipFlip proxyURL headerFlip dataFlip i;
run;
* Start of the Proc HTTP & filename;
data work._procHTTPCodeStart;
length outputCode $32000.;
outputCode = '* Create a temporary file;';
output;
outputCode = 'filename outResp temp;';
output;
outputCode = '';
output;
outputCode = '* Actual Proc HTTP Request;';
output;
outputCode = 'proc http';
output;
run;
* Procedure options for Proc HTTP;
data work._procHTTPOpt(drop=retainFlip tmpData);
length outputCode $32000. retainFlip 8. tmpData $32000.;
set work._curlString(where=(contentType='option')) end=eof;
* Remove potential quotes from url strings;
if tag in ('url', 'proxy') and substr(curlComponent, 1, 1) in ("'", '"') then curlComponent = dequote(trim(curlComponent));
* Handle data;
if tag = 'data' and retainFlip in (., 0) then do;
retainFlip = 1;
tmpData = trim(curlComponent);
end;
else if tag ne 'data' then do;
retainFlip = 0;
end;
else tmpData = catx(' ', trim(tmpData), trim(curlComponent));
* Handle everything but data;
if tag = 'url' then outputCode = "url='" || trim(curlComponent) || "'";
else if tag = 'method' then outputCode = "method='" || trim(curlComponent) || "'";
else if tag = 'proxy' then outputCode = "proxyhost='" || trim(curlComponent) || "'";
else if tag = 'options' then do;
warningMessage = catx(' ', 'WARNING: Unkown cURL option', curlComponent, ' found.');
put warningMessage;
put 'WARNING: The Proc HTTP might not be fully equivalent to the cURL command.';
end;
* Generating output;
if tag in ('url', 'method', 'proxy') then output;
else if retainFlip = 0 and tmpData ne '' then do;
outputCode = 'in="' || trim(substr(trim(tmpData), 2, length(trim(tmpData)) - 2)) || '"';
output;
end;
else if eof and tmpData ne '' then do;
outputCode = 'in="' || trim(substr(trim(tmpData), 2, length(trim(tmpData)) - 2)) || '"';
output;
end;
drop warningMessage;
retain retainFlip tmpData;
run;
* End of Procedure options for Proc HTTP;
data work._procHTTPOptEnd;
length outputCode $32000.;
* Check for SAS authentication;
if &sasAuthentication. then do;
outputCode = 'oauth_bearer = sas_services';
output;
end;
outputCode = 'out=outResp;';
output;
run;
* Procedure statements for Proc HTTP;
data work._procHTTPStmnt(drop=retainFlip tmpHeader);
length outputCode $32000. retainFlip 8. tmpHeader $32000.;
set work._curlString(where=((contentType='statement')));
* Handle headers;
if substr(curlComponent, 1, 1) in ("'", '"') then do;
retainFlip = 1;
tmpHeader = "headers '" || trim(dequote(tranwrd(curlComponent, ':', ''))) || "' = '";
end;
else if prxmatch('/"$/', trim(curlComponent)) ne 0 or prxmatch("/'$/", trim(curlComponent)) ne 0 then do;
retainFlip = 0;
outputCode = catx(' ', trim(tmpHeader), left(reverse(dequote(left(reverse(curlComponent)))))) || "';";
end;
else if tag = 'header' then do;
tmpHeader = catx(' ', trim(tmpHeader), trim(curlComponent));
end;
else retainFlip = 0;
* Handle debugging;
if curlComponent = '-v' then outputCode = 'debug level = 1;';
else if curlComponent = '--verbose' then outputCode = 'debug level = 2;';
else if curlComponent = '--trace' then outputCode = 'debug level = 3;';
if tag = 'header' and retainFlip = 0 then output;
else if curlComponent in ('-v', '--verbose', '--trace') then output;
if retainFlip = 0 then tmpHeader = '';
retain retainFlip tmpHeader;
run;
* End of the Proc HTTP;
data work._procHTTPCodeEnd;
length outputCode $32000.;
outputCode = 'run;';
run;
* Output Generation;
data work._errorOutput;
length outputCode $32000.;
output;
outputCode = '* HTTP Status Handling;';
output;
outputCode = 'data _null_;';
output;
outputCode = 'if &SYS_PROCHTTP_STATUS_CODE. lt 400 then do;';
output;
outputCode = 'put "NOTE: The request was most likely successful.";';
output;
outputCode = 'put "NOTE: The HTTP Status Code is &SYS_PROCHTTP_STATUS_CODE..";';
output;
outputCode = 'put "NOTE: That means: &SYS_PROCHTTP_STATUS_PHRASE..";';
output;
outputCode = 'end;';
output;
outputCode = 'else do;';
output;
outputCode = 'put "ERROR: The request was most likely not successful.";';
output;
outputCode = 'put "ERROR: The HTTP Status Code is &SYS_PROCHTTP_STATUS_CODE..";';
output;
outputCode = 'put "ERROR: That means: &SYS_PROCHTTP_STATUS_PHRASE..";';
output;
outputCode = 'end;';
output;
outputCode = 'run;';
output;
run;
* Handle the response;
data work._outputOption;
length outputCode $32000.;
if &outputOption. = 0 then output;
else if &outputOption. = 1 then do;
output;
outputCode = '* Print response to Log;';
output;
outputCode = 'data _null_;';
output;
outputCode = 'infile outResp;';
output;
outputCode = 'input;';
output;
outputCode = 'put _infile_;';
output;
outputCode = 'run;';
output;
end;
else if &outputOption. = 2 then do;
output;
outputCode = '* Save response to a Table;';
output;
outputCode = 'data &tableName.;';
output;
outputCode = 'infile outResp;';
output;
outputCode = 'input;';
output;
outputCode = 'responseLine = _infile_;';
output;
outputCode = 'run;';
output;
end;
else if &outputOption. = 3 then do;
output;
outputCode = '* Create a JSON library from response;';
output;
outputCode = 'libname outResp json;';
output;
end;
run;
* Combine all code snippets;
data work._allCode;
set work._procHTTPCodeStart work._procHTTPOpt work._procHTTPOptEnd work._procHTTPStmnt work._procHTTPCodeEnd work._errorOutput work._outputOption;
drop tag curlComponent contentType;
run;
* Print the code;
ods listing close;
ods html5;
title1 'cURL to Proc HTTP conversion';
title2 'Just copy and paste the code below';
title3 'Hit CTRL+SHIFT+B or use the Format code button to make it look nice';
title4 'If you see empty lines in the Proc HTTP code then the parsing might have been unsuccessful';
proc report data=work._allCode noheader;
run;
title;
ods html5 close;
* Remove datasets;
proc datasets library=work nolist;
delete _curlString _procHTTPCodeStart _procHTTPOpt _procHTTPOptEnd _procHTTPStmnt _procHTTPCodeEnd _errorOutput _allCode _outputOption;
run;