changeset 4403:ad646ff506d0

cont openapi
author Sebastien Jodogne <s.jodogne@gmail.com>
date Wed, 23 Dec 2020 18:32:13 +0100
parents b651989194d3
children f34634916d8c
files OrthancFramework/Sources/RestApi/RestApi.cpp OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp OrthancFramework/Sources/RestApi/RestApiCallDocumentation.h OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestChanges.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp OrthancServer/Sources/main.cpp
diffstat 8 files changed, 197 insertions(+), 68 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Sources/RestApi/RestApi.cpp	Wed Dec 23 15:16:37 2020 +0100
+++ b/OrthancFramework/Sources/RestApi/RestApi.cpp	Wed Dec 23 18:32:13 2020 +0100
@@ -491,14 +491,9 @@
 
     target = Json::objectValue;
 
-    target["info"]["version"] = ORTHANC_VERSION;
-    target["info"]["title"] = "Orthanc";
-
+    target["info"] = Json::objectValue;
     target["openapi"] = "3.0.0";
-
-    target["servers"].append(Json::objectValue);
-    target["servers"][0]["url"] = "https://demo.orthanc-server.com/";
-
+    target["servers"] = Json::arrayValue;
     target["paths"] = visitor.GetPaths();
   }
 }
--- a/OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp	Wed Dec 23 15:16:37 2020 +0100
+++ b/OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp	Wed Dec 23 18:32:13 2020 +0100
@@ -58,7 +58,8 @@
 
   RestApiCallDocumentation& RestApiCallDocumentation::SetRequestField(const std::string& name,
                                                                       Type type,
-                                                                      const std::string& description)
+                                                                      const std::string& description,
+                                                                      bool required)
   {
     if (method_ != HttpMethod_Post &&
         method_ != HttpMethod_Put)
@@ -77,10 +78,7 @@
     }
     else
     {
-      Parameter p;
-      p.type_ = type;
-      p.description_ = description;
-      requestFields_[name] = p;
+      requestFields_[name] = Parameter(type, description, required);
       return *this;
     }    
   }
@@ -114,10 +112,7 @@
     }
     else
     {
-      Parameter p;
-      p.type_ = type;
-      p.description_ = description;
-      uriArguments_[name] = p;
+      uriArguments_[name] = Parameter(type, description, true);
       return *this;
     }
   }
@@ -132,10 +127,7 @@
     }
     else
     {
-      Parameter p;
-      p.type_ = Type_String;
-      p.description_ = description;
-      httpHeaders_[name] = p;
+      httpHeaders_[name] = Parameter(Type_String, description, false);
       return *this;
     }
   }
@@ -143,7 +135,8 @@
 
   RestApiCallDocumentation& RestApiCallDocumentation::SetHttpGetArgument(const std::string& name,
                                                                          Type type,
-                                                                         const std::string& description)
+                                                                         const std::string& description,
+                                                                         bool required)
   {
     if (method_ != HttpMethod_Get)
     {
@@ -156,10 +149,7 @@
     }
     else
     {
-      Parameter p;
-      p.type_ = type;
-      p.description_ = description;
-      getArguments_[name] = p;
+      getArguments_[name] = Parameter(type, description, required);
       return *this;
     }
   }
@@ -180,10 +170,7 @@
     }
     else
     {
-      Parameter p;
-      p.type_ = type;
-      p.description_ = description;
-      answerFields_[name] = p;
+      answerFields_[name] = Parameter(type, description, false);
       return *this;
     }    
   }
@@ -241,9 +228,12 @@
         return "boolean";
 
       case RestApiCallDocumentation::Type_JsonObject:
-      case RestApiCallDocumentation::Type_JsonListOfStrings:
         return "object";
 
+      case RestApiCallDocumentation::Type_JsonListOfStrings:
+      case RestApiCallDocumentation::Type_JsonListOfObjects:
+        return "array";
+        
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
     }
@@ -300,8 +290,8 @@
                  field != requestFields_.end(); ++field)
             {
               Json::Value p = Json::objectValue;
-              p["type"] = TypeToString(field->second.type_);
-              p["description"] = field->second.description_;
+              p["type"] = TypeToString(field->second.GetType());
+              p["description"] = field->second.GetDescription();
               schema["properties"][field->first] = p;         
             }        
           }
@@ -322,8 +312,8 @@
                field != answerFields_.end(); ++field)
           {
             Json::Value p = Json::objectValue;
-            p["type"] = TypeToString(field->second.type_);
-            p["description"] = field->second.description_;
+            p["type"] = TypeToString(field->second.GetType());
+            p["description"] = field->second.GetDescription();
             schema["properties"][field->first] = p;         
           }        
         }
@@ -351,8 +341,9 @@
         Json::Value p = Json::objectValue;
         p["name"] = it->first;
         p["in"] = "query";
-        p["schema"]["type"] = TypeToString(it->second.type_);
-        p["description"] = it->second.description_;
+        p["required"] = it->second.IsRequired();
+        p["schema"]["type"] = TypeToString(it->second.GetType());
+        p["description"] = it->second.GetDescription();
         parameters.append(p);         
       }
 
@@ -362,8 +353,9 @@
         Json::Value p = Json::objectValue;
         p["name"] = it->first;
         p["in"] = "header";
-        p["schema"]["type"] = TypeToString(it->second.type_);
-        p["description"] = it->second.description_;
+        p["required"] = it->second.IsRequired();
+        p["schema"]["type"] = TypeToString(it->second.GetType());
+        p["description"] = it->second.GetDescription();
         parameters.append(p);         
       }
 
@@ -378,9 +370,9 @@
         Json::Value p = Json::objectValue;
         p["name"] = it->first;
         p["in"] = "path";
-        p["required"] = true;
-        p["schema"]["type"] = TypeToString(it->second.type_);
-        p["description"] = it->second.description_;
+        p["required"] = it->second.IsRequired();
+        p["schema"]["type"] = TypeToString(it->second.GetType());
+        p["description"] = it->second.GetDescription();
         parameters.append(p);         
       }
 
--- a/OrthancFramework/Sources/RestApi/RestApiCallDocumentation.h	Wed Dec 23 15:16:37 2020 +0100
+++ b/OrthancFramework/Sources/RestApi/RestApiCallDocumentation.h	Wed Dec 23 18:32:13 2020 +0100
@@ -43,14 +43,48 @@
       Type_Number,
       Type_Boolean,
       Type_JsonListOfStrings,
+      Type_JsonListOfObjects,
       Type_JsonObject
     };    
     
   private:
-    struct Parameter
+    class Parameter
     {
+    private:
       Type         type_;
       std::string  description_;
+      bool         required_;
+
+    public:
+      Parameter() :
+        type_(Type_Unknown),
+        required_(false)
+      {
+      }
+      
+      Parameter(Type type,
+                const std::string& description,
+                bool required) :
+        type_(type),
+        description_(description),
+        required_(required)
+      {
+      }
+
+      Type GetType() const
+      {
+        return type_;
+      }
+
+      const std::string& GetDescription() const
+      {
+        return description_;
+      }
+
+      bool IsRequired() const
+      {
+        return required_;
+      }
     };
     
     typedef std::map<std::string, Parameter>  Parameters;
@@ -103,7 +137,8 @@
 
     RestApiCallDocumentation& SetRequestField(const std::string& name,
                                               Type type,
-                                              const std::string& description);
+                                              const std::string& description,
+                                              bool required);
 
     RestApiCallDocumentation& AddAnswerType(MimeType type,
                                             const std::string& description);
@@ -122,7 +157,8 @@
 
     RestApiCallDocumentation& SetHttpGetArgument(const std::string& name,
                                                  Type type,
-                                                 const std::string& description);
+                                                 const std::string& description,
+                                                 bool required);
 
     RestApiCallDocumentation& SetAnswerField(const std::string& name,
                                              Type type,
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp	Wed Dec 23 15:16:37 2020 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp	Wed Dec 23 18:32:13 2020 +0100
@@ -99,6 +99,14 @@
 
   void OrthancRestApi::ResetOrthanc(RestApiPostCall& call)
   {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("System")
+        .SetSummary("Restart Orthanc");
+      return;
+    }
+
     OrthancRestApi::GetApi(call).leaveBarrier_ = true;
     OrthancRestApi::GetApi(call).resetRequestReceived_ = true;
     call.GetOutput().AnswerBuffer("{}", MimeType_Json);
@@ -107,6 +115,14 @@
 
   void OrthancRestApi::ShutdownOrthanc(RestApiPostCall& call)
   {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("System")
+        .SetSummary("Shutdown Orthanc");
+      return;
+    }
+
     OrthancRestApi::GetApi(call).leaveBarrier_ = true;
     call.GetOutput().AnswerBuffer("{}", MimeType_Json);
     LOG(WARNING) << "Shutdown request received";
@@ -132,7 +148,7 @@
       
       call.GetDocumentation()
         .SetTag("Instances")
-        .SetSummary("Upload DICOM files")
+        .SetSummary("Upload DICOM instances")
         .AddRequestType(MimeType_Dicom, "DICOM file to be uploaded")
         .AddRequestType(MimeType_Zip, "ZIP archive containing DICOM files (new in Orthanc 1.8.2)")
         .AddAnswerType(MimeType_Json, "Information about the uploaded instance, "
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestChanges.cpp	Wed Dec 23 15:16:37 2020 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestChanges.cpp	Wed Dec 23 18:32:13 2020 +0100
@@ -70,6 +70,24 @@
 
   static void GetChanges(RestApiGetCall& call)
   {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("Tracking changes")
+        .SetSummary("List changes")
+        .SetDescription("Whenever Orthanc receives a new DICOM instance, this event is recorded in the so-called _Changes Log_. This enables remote scripts to react to the arrival of new DICOM resources. A typical application is auto-routing, where an external script waits for a new DICOM instance to arrive into Orthanc, then forward this instance to another modality.")
+        .SetHttpGetArgument("limit", RestApiCallDocumentation::Type_Number, "Limit the number of results", false)
+        .SetHttpGetArgument("since", RestApiCallDocumentation::Type_Number, "Show only the resources since the provided index", false)
+        .AddAnswerType(MimeType_Json, "The list of changes")
+        .SetAnswerField("Changes", RestApiCallDocumentation::Type_JsonListOfObjects, "The individual changes")
+        .SetAnswerField("Done", RestApiCallDocumentation::Type_Boolean,
+                        "Whether the last reported change is the last of the full history")
+        .SetAnswerField("Last", RestApiCallDocumentation::Type_Number,
+                        "The index of the last reported change, can be used for the `since` argument in subsequent calls to this route")
+        .SetHttpGetSample("https://demo.orthanc-server.com/changes?since=0&limit=2", true);
+      return;
+    }
+    
     ServerContext& context = OrthancRestApi::GetContext(call);
 
     //std::string filter = GetArgument(getArguments, "filter", "");
@@ -94,6 +112,15 @@
 
   static void DeleteChanges(RestApiDeleteCall& call)
   {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("Tracking changes")
+        .SetSummary("Clear changes")
+        .SetDescription("Clear the full history stored in the changes log");
+      return;
+    }
+
     OrthancRestApi::GetIndex(call).DeleteChanges();
     call.GetOutput().AnswerBuffer("", MimeType_PlainText);
   }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Wed Dec 23 15:16:37 2020 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Wed Dec 23 18:32:13 2020 +0100
@@ -168,10 +168,10 @@
         .SetTag(GetResourceTypeText(resourceType, true /* plural */, true /* upper case */))
         .SetSummary("List the available " + resources)
         .SetDescription("List the Orthanc identifiers of all the available DICOM " + resources)
-        .SetHttpGetArgument("limit", RestApiCallDocumentation::Type_Number, "Limit the number of results")
-        .SetHttpGetArgument("since", RestApiCallDocumentation::Type_Number, "Show only the resources since the provided index")
+        .SetHttpGetArgument("limit", RestApiCallDocumentation::Type_Number, "Limit the number of results", false)
+        .SetHttpGetArgument("since", RestApiCallDocumentation::Type_Number, "Show only the resources since the provided index", false)
         .SetHttpGetArgument("expand", RestApiCallDocumentation::Type_String,
-                            "If present, retrieve detailed information about the individual " + resources)
+                            "If present, retrieve detailed information about the individual " + resources, false)
         .SetHttpGetSample("https://demo.orthanc-server.com/" + resources + "?since=0&limit=2", true);
       return;
     }
@@ -283,7 +283,7 @@
         .SetTag("Patients")
         .SetSummary("Is the patient protected against recycling?")
         .SetUriArgument("id", RestApiCallDocumentation::Type_String, "Orthanc identifier of the patient of interest")
-        .AddAnswerType(MimeType_PlainText, "\"1\" if protected, \"0\" if not protected");
+        .AddAnswerType(MimeType_PlainText, "`1` if protected, `0` if not protected");
       return;
     }
     
@@ -300,7 +300,7 @@
       call.GetDocumentation()
         .SetTag("Patients")
         .SetSummary("Protect one patient against recycling")
-        .SetDescription("Check out configuration options \"MaximumStorageSize\" and \"MaximumPatientCount\"")
+        .SetDescription("Check out configuration options `MaximumStorageSize` and `MaximumPatientCount`")
         .SetUriArgument("id", RestApiCallDocumentation::Type_String, "Orthanc identifier of the patient of interest");
       return;
     }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Wed Dec 23 15:16:37 2020 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp	Wed Dec 23 18:32:13 2020 +0100
@@ -69,14 +69,14 @@
         .SetAnswerField("IsHttpServerSecure", RestApiCallDocumentation::Type_Boolean,
                         "Whether the REST API is properly secured (assuming no reverse proxy is in use): https://book.orthanc-server.com/faq/security.html#securing-the-http-server")
         .SetAnswerField("StorageAreaPlugin", RestApiCallDocumentation::Type_String,
-                        "Information about the installed storage area plugin (\"null\" if no such plugin is installed)")
+                        "Information about the installed storage area plugin (`null` if no such plugin is installed)")
         .SetAnswerField("DatabaseBackendPlugin", RestApiCallDocumentation::Type_String,
-                        "Information about the installed database index plugin (\"null\" if no such plugin is installed)")
+                        "Information about the installed database index plugin (`null` if no such plugin is installed)")
         .SetAnswerField("DicomAet", RestApiCallDocumentation::Type_String, "The DICOM AET of Orthanc")
         .SetAnswerField("DicomPort", RestApiCallDocumentation::Type_Number, "The port to the DICOM server of Orthanc")
         .SetAnswerField("HttpPort", RestApiCallDocumentation::Type_Number, "The port to the HTTP server of Orthanc")
         .SetAnswerField("Name", RestApiCallDocumentation::Type_String,
-                        "The name of the Orthanc server, cf. the \"Name\" configuration option")
+                        "The name of the Orthanc server, cf. the `Name` configuration option")
         .SetAnswerField("PluginsEnabled", RestApiCallDocumentation::Type_Boolean,
                         "Whether Orthanc was built with support for plugins")
         .SetHttpGetSample("https://demo.orthanc-server.com/system", true);
@@ -131,15 +131,15 @@
     {
       call.GetDocumentation()
         .SetTag("System")
-        .SetSummary("Get statistics")
-        .SetDescription("Get some statistics about Orthanc")
+        .SetSummary("Get database statistics")
+        .SetDescription("Get statistics related to the database of Orthanc")
         .SetAnswerField("CountInstances", RestApiCallDocumentation::Type_Number, "Number of DICOM instances stored in Orthanc")
         .SetAnswerField("CountSeries", RestApiCallDocumentation::Type_Number, "Number of DICOM series stored in Orthanc")
         .SetAnswerField("CountStudies", RestApiCallDocumentation::Type_Number, "Number of DICOM studies stored in Orthanc")
         .SetAnswerField("CountPatients", RestApiCallDocumentation::Type_Number, "Number of patients stored in Orthanc")
         .SetAnswerField("TotalDiskSize", RestApiCallDocumentation::Type_String, "Size of the storage area (in bytes)")
         .SetAnswerField("TotalDiskSizeMB", RestApiCallDocumentation::Type_Number, "Size of the storage area (in megabytes)")
-        .SetAnswerField("TotalUncompressedSize", RestApiCallDocumentation::Type_String, "Total size of all the files once uncompressed (in bytes). This corresponds to \"TotalDiskSize\" if no compression is enabled, cf. \"StorageCompression\" configuration option")
+        .SetAnswerField("TotalUncompressedSize", RestApiCallDocumentation::Type_String, "Total size of all the files once uncompressed (in bytes). This corresponds to `TotalDiskSize` if no compression is enabled, cf. `StorageCompression` configuration option")
         .SetAnswerField("TotalUncompressedSizeMB", RestApiCallDocumentation::Type_Number, "Total size of all the files once uncompressed (in megabytes)")
         .SetHttpGetSample("https://demo.orthanc-server.com/statistics", true);
       return;
@@ -173,7 +173,7 @@
         .SetSummary("Generate an identifier")
         .SetDescription("Generate a random DICOM identifier")
         .SetHttpGetArgument("level", RestApiCallDocumentation::Type_String,
-                            "Type of DICOM resource among: \"patient\", \"study\", \"series\" or \"instance\"")
+                            "Type of DICOM resource among: `patient`, `study`, `series` or `instance`", true)
         .AddAnswerType(MimeType_PlainText, "The generated identifier");
       return;
     }
@@ -205,7 +205,7 @@
         .SetTag("System")
         .SetSummary("Execute Lua script")
         .SetDescription("Execute the provided Lua script by the Orthanc server. This is very insecure for "
-                        "Orthanc servers that are remotely accessible, cf. configuration option \"ExecuteLuaEnabled\"")
+                        "Orthanc servers that are remotely accessible, cf. configuration option `ExecuteLuaEnabled`")
         .AddRequestType(MimeType_PlainText, "The Lua script to be executed")
         .AddAnswerType(MimeType_PlainText, "Output of the Lua script");
       return;
@@ -253,6 +253,16 @@
 
   static void GetDicomConformanceStatement(RestApiGetCall& call)
   {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("System")
+        .SetSummary("Get DICOM conformance")
+        .SetDescription("Get the DICOM conformance statement of Orthanc")
+        .AddAnswerType(MimeType_PlainText, "The DICOM conformance statement");
+      return;
+    }
+
     std::string statement;
     GetFileResource(statement, ServerResources::DICOM_CONFORMANCE_STATEMENT);
     call.GetOutput().AnswerBuffer(statement, MimeType_PlainText);
@@ -261,6 +271,18 @@
 
   static void GetDefaultEncoding(RestApiGetCall& call)
   {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("System")
+        .SetSummary("Get default encoding")
+        .SetDescription("Get the default encoding that is used by Orthanc if parsing "
+                        "a DICOM instance without the `SpecificCharacterEncoding` tag, or during C-FIND. "
+                        "This corresponds to the configuration option `DefaultEncoding`.")
+        .AddAnswerType(MimeType_PlainText, "The name of the encoding");
+      return;
+    }
+
     Encoding encoding = GetDefaultDicomEncoding();
     call.GetOutput().AnswerBuffer(EnumerationToString(encoding), MimeType_PlainText);
   }
@@ -268,6 +290,19 @@
 
   static void SetDefaultEncoding(RestApiPutCall& call)
   {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("System")
+        .SetSummary("Set default encoding")
+        .SetDescription("Change the default encoding that is used by Orthanc if parsing "
+                        "a DICOM instance without the `SpecificCharacterEncoding` tag, or during C-FIND. "
+                        "This corresponds to the configuration option `DefaultEncoding`.")
+        .AddRequestType(MimeType_PlainText, "The name of the encoding. Check out configuration "
+                        "option `DefaultEncoding` for the allowed values.");
+      return;
+    }
+
     std::string body;
     call.BodyToString(body);
 
@@ -508,8 +543,8 @@
     {
       call.GetDocumentation()
         .SetTag("System")
-        .SetSummary("Get metrics")
-        .SetDescription("Get metrics in the Prometheus file format")
+        .SetSummary("Get usage metrics")
+        .SetDescription("Get usage metrics of Orthanc in the Prometheus file format (OpenMetrics)")
         .SetHttpGetSample("https://demo.orthanc-server.com/tools/metrics-prometheus", false);
       return;
     }
@@ -551,6 +586,17 @@
 
   static void GetMetricsEnabled(RestApiGetCall& call)
   {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("System")
+        .SetSummary("Are metrics collected?")
+        .SetDescription("Returns a Boolean specifying whether Prometheus metrics "
+                        "are collected and exposed at `/tools/metrics-prometheus`")
+        .AddAnswerType(MimeType_PlainText, "`1` if metrics are collected, `0` if metrics are disabled");
+      return;
+    }
+
     bool enabled = OrthancRestApi::GetContext(call).GetMetricsRegistry().IsEnabled();
     call.GetOutput().AnswerBuffer(enabled ? "1" : "0", MimeType_PlainText);
   }
@@ -558,6 +604,16 @@
 
   static void PutMetricsEnabled(RestApiPutCall& call)
   {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("System")
+        .SetSummary("Enable collection of metrics")
+        .SetDescription("Enable or disable the collection and publication of metrics at `/tools/metrics-prometheus`")
+        .AddRequestType(MimeType_PlainText, "`1` if metrics are collected, `0` if metrics are disabled");
+      return;
+    }
+
     bool enabled;
 
     std::string body;
@@ -591,7 +647,7 @@
         .SetTag("Logs")
         .SetSummary("Get main log level")
         .SetDescription("Get the main log level of Orthanc")
-        .AddAnswerType(MimeType_PlainText, "Possible values: \"default\", \"verbose\" or \"trace\"");
+        .AddAnswerType(MimeType_PlainText, "Possible values: `default`, `verbose` or `trace`");
       return;
     }
 
@@ -608,7 +664,7 @@
         .SetTag("Logs")
         .SetSummary("Set main log level")
         .SetDescription("Set the main log level of Orthanc")
-        .AddRequestType(MimeType_PlainText, "Possible values: \"default\", \"verbose\" or \"trace\"");
+        .AddRequestType(MimeType_PlainText, "Possible values: `default`, `verbose` or `trace`");
       return;
     }
 
@@ -649,9 +705,9 @@
       std::string category = Logging::GetCategoryName(GetCategory(call));
       call.GetDocumentation()
         .SetTag("Logs")
-        .SetSummary("Get log level for \"" + category + "\"")
-        .SetDescription("Get the log level of the log category \"" + category + "\"")
-        .AddAnswerType(MimeType_PlainText, "Possible values: \"default\", \"verbose\" or \"trace\"");
+        .SetSummary("Get log level for `" + category + "`")
+        .SetDescription("Get the log level of the log category `" + category + "`")
+        .AddAnswerType(MimeType_PlainText, "Possible values: `default`, `verbose` or `trace`");
       return;
     }
 
@@ -667,9 +723,9 @@
       std::string category = Logging::GetCategoryName(GetCategory(call));
       call.GetDocumentation()
         .SetTag("Logs")
-        .SetSummary("Set log level for \"" + category + "\"")
-        .SetDescription("Set the log level of the log category \"" + category + "\"")
-        .AddRequestType(MimeType_PlainText, "Possible values: \"default\", \"verbose\" or \"trace\"");
+        .SetSummary("Set log level for `" + category + "`")
+        .SetDescription("Set the log level of the log category `" + category + "`")
+        .AddRequestType(MimeType_PlainText, "Possible values: `default`, `verbose` or `trace`");
       return;
     }
 
--- a/OrthancServer/Sources/main.cpp	Wed Dec 23 15:16:37 2020 +0100
+++ b/OrthancServer/Sources/main.cpp	Wed Dec 23 18:32:13 2020 +0100
@@ -1766,6 +1766,13 @@
           restApi.GenerateOpenApiDocumentation(openapi);
           context.Stop();
         }
+
+        openapi["info"]["version"] = ORTHANC_VERSION;
+        openapi["info"]["title"] = "Orthanc API";
+
+        Json::Value server = Json::objectValue;
+        server["url"] = "https://demo.orthanc-server.com/";
+        openapi["servers"].append(server);
         
         std::string s;
         Toolbox::WriteStyledJson(s, openapi);