changeset 4405:5466f336b09f

gathering statistics about progress of api documentation
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 24 Dec 2020 09:37:30 +0100
parents f34634916d8c
children 4cb9f66a1c7c
files OrthancFramework/Sources/RestApi/RestApi.cpp OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp OrthancFramework/Sources/RestApi/RestApiCallDocumentation.h OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp
diffstat 4 files changed, 107 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Sources/RestApi/RestApi.cpp	Thu Dec 24 08:59:45 2020 +0100
+++ b/OrthancFramework/Sources/RestApi/RestApi.cpp	Thu Dec 24 09:37:30 2020 +0100
@@ -27,6 +27,7 @@
 #include "../Logging.h"
 #include "../OrthancException.h"
 
+#include <boost/math/special_functions/round.hpp>
 #include <stdlib.h>   // To define "_exit()" under Windows
 #include <stdio.h>
 
@@ -131,10 +132,15 @@
     private:
       RestApi&    restApi_;
       Json::Value paths_;
+      size_t      successPathsCount_;
+      size_t      totalPathsCount_;
   
     public:
       explicit OpenApiVisitor(RestApi& restApi) :
-        restApi_(restApi)
+        restApi_(restApi),
+        paths_(Json::objectValue),
+        successPathsCount_(0),
+        totalPathsCount_(0)
       {
       }
   
@@ -144,7 +150,11 @@
                          const HttpToolbox::Arguments& components,
                          const UriComponents& trailing)
       {
-        const std::string path = Toolbox::FlattenUri(uri);
+        std::string path = Toolbox::FlattenUri(uri);
+        if (hasTrailing)
+        {
+          path += "/{...}";
+        }
 
         if (paths_.isMember(path))
         {
@@ -160,8 +170,15 @@
           uriArguments.insert(it->first.c_str());
         }
 
+        if (hasTrailing)
+        {
+          uriArguments.insert("...");
+        }
+
         if (resource.HasHandler(HttpMethod_Get))
         {
+          totalPathsCount_ ++;
+          
           StringHttpOutput o1;
           HttpOutput o2(o1, false);
           RestApiOutput o3(o2, HttpMethod_Get);
@@ -189,6 +206,7 @@
           if (ok)
           {
             paths_[path]["get"] = v;
+            successPathsCount_ ++;
           }
           else
           {
@@ -198,6 +216,8 @@
     
         if (resource.HasHandler(HttpMethod_Post))
         {
+          totalPathsCount_ ++;
+          
           StringHttpOutput o1;
           HttpOutput o2(o1, false);
           RestApiOutput o3(o2, HttpMethod_Post);
@@ -224,6 +244,7 @@
           if (ok)
           {
             paths_[path]["post"] = v;
+            successPathsCount_ ++;
           }
           else
           {
@@ -233,6 +254,8 @@
     
         if (resource.HasHandler(HttpMethod_Delete))
         {
+          totalPathsCount_ ++;
+          
           StringHttpOutput o1;
           HttpOutput o2(o1, false);
           RestApiOutput o3(o2, HttpMethod_Delete);
@@ -259,6 +282,7 @@
           if (ok)
           {
             paths_[path]["delete"] = v;
+            successPathsCount_ ++;
           }
           else
           {
@@ -268,6 +292,8 @@
 
         if (resource.HasHandler(HttpMethod_Put))
         {
+          totalPathsCount_ ++;
+          
           StringHttpOutput o1;
           HttpOutput o2(o1, false);
           RestApiOutput o3(o2, HttpMethod_Put);
@@ -294,6 +320,7 @@
           if (ok)
           {
             paths_[path]["put"] = v;
+            successPathsCount_ ++;
           }
           else
           {
@@ -309,6 +336,16 @@
       {
         return paths_;
       }
+
+      size_t GetSuccessPathsCount() const
+      {
+        return successPathsCount_;
+      }
+
+      size_t GetTotalPathsCount() const
+      {
+        return totalPathsCount_;
+      }
     };
   }
 
@@ -495,5 +532,18 @@
     target["openapi"] = "3.0.0";
     target["servers"] = Json::arrayValue;
     target["paths"] = visitor.GetPaths();
+
+    assert(visitor.GetSuccessPathsCount() <= visitor.GetTotalPathsCount());
+    size_t total = visitor.GetTotalPathsCount();
+    if (total == 0)
+    {
+      total = 1;  // Avoid division by zero
+    }
+    float coverage = (100.0f * static_cast<float>(visitor.GetSuccessPathsCount()) /
+                      static_cast<float>(total));
+    
+    LOG(WARNING) << "The documentation of the REST API contains " << visitor.GetSuccessPathsCount()
+                 << " paths over a total of " << visitor.GetTotalPathsCount() << " paths "
+                 << "(" << static_cast<unsigned int>(boost::math::iround(coverage)) << "%)";
   }
 }
--- a/OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp	Thu Dec 24 08:59:45 2020 +0100
+++ b/OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp	Thu Dec 24 09:37:30 2020 +0100
@@ -103,6 +103,7 @@
   
 
   RestApiCallDocumentation& RestApiCallDocumentation::SetUriArgument(const std::string& name,
+                                                                     Type type,
                                                                      const std::string& description)
   {
     if (uriArguments_.find(name) != uriArguments_.end())
@@ -111,7 +112,7 @@
     }
     else
     {
-      uriArguments_[name] = Parameter(Type_String, description, true);
+      uriArguments_[name] = Parameter(type, description, true);
       return *this;
     }
   }
@@ -318,13 +319,24 @@
         }
       }
       
-      if (sampleJson_.type() != Json::nullValue)
+      for (AllowedTypes::const_iterator it = answerTypes_.begin();
+           it != answerTypes_.end(); ++it)
       {
-        target["responses"]["200"]["content"][EnumerationToString(MimeType_Json)]["schema"]["example"] = sampleJson_;
-      }
-      else if (answerTypes_.find(MimeType_Json) != answerTypes_.end())
-      {
-        target["responses"]["200"]["content"][EnumerationToString(MimeType_Json)]["examples"] = Json::objectValue;
+        if (it->first == MimeType_Json &&
+            sampleJson_.type() != Json::nullValue)
+        {
+          target["responses"]["200"]["content"][EnumerationToString(MimeType_Json)]["schema"]["example"] = sampleJson_;
+        }
+        else if (it->first == MimeType_PlainText &&
+                 hasSampleText_)
+        {
+          // Handled below
+        }
+        else
+        {
+          // No sample for this MIME type
+          target["responses"]["200"]["content"][EnumerationToString(it->first)]["examples"] = Json::objectValue;
+        }
       }
 
       if (hasSampleText_)
--- a/OrthancFramework/Sources/RestApi/RestApiCallDocumentation.h	Thu Dec 24 08:59:45 2020 +0100
+++ b/OrthancFramework/Sources/RestApi/RestApiCallDocumentation.h	Thu Dec 24 09:37:30 2020 +0100
@@ -144,8 +144,15 @@
                                             const std::string& description);
 
     RestApiCallDocumentation& SetUriArgument(const std::string& name,
+                                             Type type,
                                              const std::string& description);
 
+    RestApiCallDocumentation& SetUriArgument(const std::string& name,
+                                             const std::string& description)
+    {
+      return SetUriArgument(name, Type_String, description);
+    }
+
     bool HasUriArgument(const std::string& name) const
     {
       return (uriArguments_.find(name) != uriArguments_.end());
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Thu Dec 24 08:59:45 2020 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Thu Dec 24 09:37:30 2020 +0100
@@ -336,6 +336,20 @@
  
   static void GetInstanceFile(RestApiGetCall& call)
   {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("Instances")
+        .SetSummary("Download DICOM")
+        .SetDescription("Download one DICOM instance")
+        .SetUriArgument("id", "Orthanc identifier of the DICOM instance of interest")
+        .SetHttpHeader("Accept", "This HTTP header can be set to retrieve the DICOM instance in DICOMweb format")
+        .AddAnswerType(MimeType_Dicom, "The DICOM instance")
+        .AddAnswerType(MimeType_DicomWebJson, "The DICOM instance, in DICOMweb JSON format")
+        .AddAnswerType(MimeType_DicomWebXml, "The DICOM instance, in DICOMweb XML format");
+      return;
+    }
+
     ServerContext& context = OrthancRestApi::GetContext(call);
 
     std::string publicId = call.GetUriComponent("id", "");
@@ -1570,6 +1584,21 @@
 
   static void GetRawContent(RestApiGetCall& call)
   {
+    if (call.IsDocumentation())
+    {
+      call.GetDocumentation()
+        .SetTag("Instances")
+        .SetSummary("Get raw tag")
+        .SetDescription("Get the raw content of one DICOM tag in the hierarchy of DICOM dataset")
+        .SetUriArgument("id", "Orthanc identifier of the DICOM instance of interest")
+        .SetUriArgument("...", "Path to the DICOM tag. This is the interleaving of one DICOM tag, possibly followed "
+                        "by an index for sequences. Sequences are accessible as, for instance, `/0008-1140/1/0008-1150`")
+        .AddAnswerType(MimeType_Binary, "The raw value of the tag of intereset "
+                       "(binary data, whose memory layout depends on the underlying transfer syntax), "
+                       "or JSON array containing the list of available tags if accessing a dataset");
+      return;
+    }
+
     std::string id = call.GetUriComponent("id", "");
 
     ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), id);