Mercurial > hg > orthanc
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 } |