comparison OrthancFramework/Sources/RestApi/RestApi.cpp @ 4399:80fd140b12ba

New command-line option: "--openapi" to write the OpenAPI documentation of the REST API to a file
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 23 Dec 2020 12:21:03 +0100
parents a01b1c9cbef4
children 029366f95217
comparison
equal deleted inserted replaced
4398:38c22715bb56 4399:80fd140b12ba
21 21
22 22
23 #include "../PrecompiledHeaders.h" 23 #include "../PrecompiledHeaders.h"
24 #include "RestApi.h" 24 #include "RestApi.h"
25 25
26 #include "../HttpServer/StringHttpOutput.h"
26 #include "../Logging.h" 27 #include "../Logging.h"
28 #include "../OrthancException.h"
27 29
28 #include <stdlib.h> // To define "_exit()" under Windows 30 #include <stdlib.h> // To define "_exit()" under Windows
29 #include <stdio.h> 31 #include <stdio.h>
30 32
31 namespace Orthanc 33 namespace Orthanc
71 { 73 {
72 } 74 }
73 75
74 virtual bool Visit(const RestApiHierarchy::Resource& resource, 76 virtual bool Visit(const RestApiHierarchy::Resource& resource,
75 const UriComponents& uri, 77 const UriComponents& uri,
78 bool hasTrailing,
76 const HttpToolbox::Arguments& components, 79 const HttpToolbox::Arguments& components,
77 const UriComponents& trailing) 80 const UriComponents& trailing)
78 { 81 {
79 if (resource.HasHandler(method_)) 82 if (resource.HasMethod(method_))
80 { 83 {
81 switch (method_) 84 switch (method_)
82 { 85 {
83 case HttpMethod_Get: 86 case HttpMethod_Get:
84 { 87 {
118 } 121 }
119 122
120 return false; 123 return false;
121 } 124 }
122 }; 125 };
126
127
128
129 class OpenApiVisitor : public RestApiHierarchy::IVisitor
130 {
131 private:
132 RestApi& restApi_;
133 Json::Value paths_;
134
135 public:
136 OpenApiVisitor(RestApi& restApi) :
137 restApi_(restApi)
138 {
139 }
140
141 virtual bool Visit(const RestApiHierarchy::Resource& resource,
142 const UriComponents& uri,
143 bool hasTrailing,
144 const HttpToolbox::Arguments& components,
145 const UriComponents& trailing)
146 {
147 const std::string path = Toolbox::FlattenUri(uri);
148
149 if (hasTrailing)
150 LOG(WARNING) << ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> " << path;
151
152 if (paths_.isMember(path))
153 {
154 throw OrthancException(ErrorCode_InternalError);
155 }
156
157 //if (path == "/patients/{id}/protected")
158 //asm("int $3");
159
160 if (resource.HasMethod(HttpMethod_Get))
161 {
162 StringHttpOutput o1;
163 HttpOutput o2(o1, false);
164 RestApiOutput o3(o2, HttpMethod_Get);
165 RestApiGetCall call(o3, restApi_, RequestOrigin_Documentation, "" /* remote IP */,
166 "" /* username */, HttpToolbox::Arguments() /* HTTP headers */,
167 HttpToolbox::Arguments() /* URI components */,
168 UriComponents() /* trailing */,
169 uri, HttpToolbox::Arguments() /* GET arguments */);
170
171 bool ok = false;
172 Json::Value v;
173
174 try
175 {
176 ok = (resource.Handle(call) &&
177 call.GetDocumentation().FormatOpenApi(v));
178 }
179 catch (OrthancException&)
180 {
181 }
182 catch (boost::bad_lexical_cast&)
183 {
184 }
185
186 if (ok)
187 {
188 paths_[path]["get"] = v;
189 }
190 else
191 {
192 LOG(WARNING) << "Ignoring URI without API documentation: GET " << path;
193 }
194 }
195
196 if (resource.HasMethod(HttpMethod_Post))
197 {
198 StringHttpOutput o1;
199 HttpOutput o2(o1, false);
200 RestApiOutput o3(o2, HttpMethod_Post);
201 RestApiPostCall call(o3, restApi_, RequestOrigin_Documentation, "" /* remote IP */,
202 "" /* username */, HttpToolbox::Arguments() /* HTTP headers */,
203 HttpToolbox::Arguments() /* URI components */,
204 UriComponents() /* trailing */, uri, NULL /* body */, 0 /* body size */);
205
206 bool ok = false;
207 Json::Value v;
208
209 try
210 {
211 ok = (resource.Handle(call) &&
212 call.GetDocumentation().FormatOpenApi(v));
213 }
214 catch (OrthancException&)
215 {
216 }
217 catch (boost::bad_lexical_cast&)
218 {
219 }
220
221 if (ok)
222 {
223 paths_[path]["post"] = v;
224 }
225 else
226 {
227 LOG(WARNING) << "Ignoring URI without API documentation: POST " << path;
228 }
229 }
230
231 if (resource.HasMethod(HttpMethod_Delete))
232 {
233 StringHttpOutput o1;
234 HttpOutput o2(o1, false);
235 RestApiOutput o3(o2, HttpMethod_Delete);
236 RestApiDeleteCall call(o3, restApi_, RequestOrigin_Documentation, "" /* remote IP */,
237 "" /* username */, HttpToolbox::Arguments() /* HTTP headers */,
238 HttpToolbox::Arguments() /* URI components */,
239 UriComponents() /* trailing */, uri);
240
241 bool ok = false;
242 Json::Value v;
243
244 try
245 {
246 ok = (resource.Handle(call) &&
247 call.GetDocumentation().FormatOpenApi(v));
248 }
249 catch (OrthancException&)
250 {
251 }
252 catch (boost::bad_lexical_cast&)
253 {
254 }
255
256 if (ok)
257 {
258 paths_[path]["delete"] = v;
259 }
260 else
261 {
262 LOG(WARNING) << "Ignoring URI without API documentation: DELETE " << path;
263 }
264 }
265
266 if (resource.HasMethod(HttpMethod_Put))
267 {
268 StringHttpOutput o1;
269 HttpOutput o2(o1, false);
270 RestApiOutput o3(o2, HttpMethod_Put);
271 RestApiPutCall call(o3, restApi_, RequestOrigin_Documentation, "" /* remote IP */,
272 "" /* username */, HttpToolbox::Arguments() /* HTTP headers */,
273 HttpToolbox::Arguments() /* URI components */,
274 UriComponents() /* trailing */, uri, NULL /* body */, 0 /* body size */);
275
276 bool ok = false;
277 Json::Value v;
278
279 try
280 {
281 ok = (resource.Handle(call) &&
282 call.GetDocumentation().FormatOpenApi(v));
283 }
284 catch (OrthancException&)
285 {
286 }
287 catch (boost::bad_lexical_cast&)
288 {
289 }
290
291 if (ok)
292 {
293 paths_[path]["put"] = v;
294 }
295 else
296 {
297 LOG(WARNING) << "Ignoring URI without API documentation: PUT " << path;
298 }
299 }
300
301 return true;
302 }
303
304
305 const Json::Value& GetPaths() const
306 {
307 return paths_;
308 }
309 };
123 } 310 }
124 311
125 312
126 313
127 static void AddMethod(std::string& target, 314 static void AddMethod(std::string& target,
158 } 345 }
159 346
160 return s; 347 return s;
161 } 348 }
162 349
350
351
352 bool RestApi::CreateChunkedRequestReader(std::unique_ptr<IChunkedRequestReader>& target,
353 RequestOrigin origin,
354 const char* remoteIp,
355 const char* username,
356 HttpMethod method,
357 const UriComponents& uri,
358 const HttpToolbox::Arguments& headers)
359 {
360 return false;
361 }
163 362
164 363
165 bool RestApi::Handle(HttpOutput& output, 364 bool RestApi::Handle(HttpOutput& output,
166 RequestOrigin origin, 365 RequestOrigin origin,
167 const char* remoteIp, 366 const char* remoteIp,
253 { 452 {
254 root_.Register(path, handler); 453 root_.Register(path, handler);
255 } 454 }
256 455
257 void RestApi::AutoListChildren(RestApiGetCall& call) 456 void RestApi::AutoListChildren(RestApiGetCall& call)
258 { 457 {
458 call.GetDocumentation()
459 .SetTag("Other")
460 .SetSummary("List of operations")
461 .SetDescription("List the available operations under URI: " + call.FlattenUri())
462 .AddAnswerType(MimeType_Json, "List of the available operations");
463
259 RestApi& context = call.GetContext(); 464 RestApi& context = call.GetContext();
260 465
261 Json::Value directory; 466 Json::Value directory;
262 if (context.root_.GetDirectory(directory, call.GetFullUri())) 467 if (context.root_.GetDirectory(directory, call.GetFullUri()))
263 { 468 {
264 call.GetOutput().AnswerJson(directory); 469 if (call.IsDocumentation())
470 {
471 call.GetDocumentation().SetSample(directory);
472 }
473 else
474 {
475 call.GetOutput().AnswerJson(directory);
476 }
265 } 477 }
266 } 478 }
479
480
481 void RestApi::GenerateOpenApiDocumentation(Json::Value& target)
482 {
483 OpenApiVisitor visitor(*this);
484
485 UriComponents root;
486 root_.ExploreAllResources(visitor, root);
487
488 target = Json::objectValue;
489
490 target["info"]["version"] = ORTHANC_VERSION;
491 target["info"]["title"] = "Orthanc";
492
493 target["openapi"] = "3.0.0";
494
495 target["servers"].append(Json::objectValue);
496 target["servers"][0]["url"] = "https://demo.orthanc-server.com/";
497
498 target["paths"] = visitor.GetPaths();
499 }
267 } 500 }