changeset 4413:22a1352a0823

cont openapi
author Sebastien Jodogne <s.jodogne@gmail.com>
date Mon, 28 Dec 2020 13:08:00 +0100
parents 68b96234fbd6
children d928dfcacb4b
files OrthancFramework/Sources/RestApi/RestApi.cpp OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp OrthancServer/Sources/main.cpp
diffstat 5 files changed, 210 insertions(+), 132 deletions(-) [+]
line wrap: on
line diff
--- a/OrthancFramework/Sources/RestApi/RestApi.cpp	Mon Dec 28 11:57:48 2020 +0100
+++ b/OrthancFramework/Sources/RestApi/RestApi.cpp	Mon Dec 28 13:08:00 2020 +0100
@@ -425,11 +425,14 @@
       class Path
       {
       private:
-        std::string tag_;
         bool        hasGet_;
         bool        hasPost_;
         bool        hasDelete_;
         bool        hasPut_;
+        std::string getTag_;
+        std::string postTag_;
+        std::string deleteTag_;
+        std::string putTag_;
         std::string summary_;
         HttpMethod  summaryOrigin_;
 
@@ -443,24 +446,49 @@
         {
         }
 
-        void AddMethod(HttpMethod method)
+        void AddMethod(HttpMethod method,
+                       const std::string& tag)
         {
           switch (method)
           {
             case HttpMethod_Get:
+              if (hasGet_)
+              {
+                throw OrthancException(ErrorCode_InternalError);
+              }
+              
               hasGet_ = true;
+              getTag_ = tag;
               break;
               
             case HttpMethod_Post:
+              if (hasPost_)
+              {
+                throw OrthancException(ErrorCode_InternalError);
+              }
+              
               hasPost_ = true;
+              postTag_ = tag;
               break;
               
             case HttpMethod_Delete:
+              if (hasDelete_)
+              {
+                throw OrthancException(ErrorCode_InternalError);
+              }
+              
               hasDelete_ = true;
+              deleteTag_ = tag;
               break;
               
             case HttpMethod_Put:
+              if (hasPut_)
+              {
+                throw OrthancException(ErrorCode_InternalError);
+              }
+              
               hasPut_ = true;
+              putTag_ = tag;
               break;
 
             default:
@@ -468,34 +496,9 @@
           }
         }
 
-        bool HasSummary() const
-        {
-          return !summary_.empty();
-        }
-
-        const std::string& GetTag() const
-        {
-          return tag_;
-        }
-
-        void SetSummary(const std::string& tag,
-                        const std::string& summary,
+        void SetSummary(const std::string& summary,
                         HttpMethod newOrigin)
         {
-          if (!tag_.empty() &&
-              !tag.empty() &&
-              tag_ != tag)
-          {
-            printf("===================================================================================\n");
-            throw OrthancException(ErrorCode_InternalError, "Mismatch between HTTP methods in the tag: \"" +
-                                   tag + "\" vs. \"" + tag_ + "\"");
-          }
-
-          if (tag_.empty())
-          {
-            tag_ = tag;
-          }
-          
           if (!summary.empty())
           {
             bool replace;
@@ -544,110 +547,132 @@
           }
         }
 
-        bool HasGet() const
-        {
-          return hasGet_;
-        }
-
-        bool HasPost() const
-        {
-          return hasPost_;
-        }
-
-        bool HasDelete() const
-        {
-          return hasDelete_;
-        }
-
-        bool HasPut() const
-        {
-          return hasPut_;
-        }
-
         const std::string& GetSummary() const
         {
           return summary_;
         }
+
+        static std::string FormatTag(const std::string& tag)
+        {
+          if (tag.empty())
+          {
+            return tag;
+          }
+          else
+          {
+            std::string s;
+            s.reserve(tag.size());
+            s.push_back(tag[0]);
+
+            for (size_t i = 1; i < tag.size(); i++)
+            {
+              if (tag[i] == ' ')
+              {
+                s.push_back('-');
+              }
+              else if (isupper(tag[i]) &&
+                       tag[i - 1] == ' ')
+              {
+                s.push_back(tolower(tag[i]));
+              }
+              else
+              {
+                s.push_back(tag[i]);
+              }
+            }
+
+            return s;
+          }
+        }
+
+        std::string Format(const std::string& openApiUrl,
+                           HttpMethod method,
+                           const std::string& uri) const
+        {
+          std::string p = uri;
+          boost::replace_all(p, "/", "~1");
+          
+          switch (method)
+          {
+            case HttpMethod_Get:
+              if (hasGet_)
+              {
+                if (openApiUrl.empty())
+                {
+                  return "GET";
+                }
+                else
+                {
+                  return ("`GET <" + openApiUrl + "#tag/" + FormatTag(getTag_) + "/paths/" + p + "/get>`__");
+                }
+              }
+              break;
+              
+            case HttpMethod_Post:
+              if (hasPost_)
+              {
+                if (openApiUrl.empty())
+                {
+                  return "POST";
+                }
+                else
+                {
+                  return ("`POST <" + openApiUrl + "#tag/" + FormatTag(postTag_) + "/paths/" + p + "/post>`__");
+                }
+              }
+              break;
+              
+            case HttpMethod_Delete:
+              if (hasDelete_)
+              {
+                if (openApiUrl.empty())
+                {
+                  return "DELETE";
+                }
+                else
+                {
+                  return ("`DELETE <" + openApiUrl + "#tag/" + FormatTag(deleteTag_) + "/paths/" + p + "/delete>`__");
+                }
+              }
+              break;
+              
+            case HttpMethod_Put:
+              if (hasPut_)
+              {
+                if (openApiUrl.empty())
+                {
+                  return "GET";
+                }
+                else
+                {
+                  return ("`PUT <" + openApiUrl + "#tag/" + FormatTag(putTag_) + "/paths/" + p + "/put>`__");
+                }
+              }
+              break;
+
+            default:
+              throw OrthancException(ErrorCode_InternalError);
+          }
+
+          return "";
+        }
       };
 
       typedef std::map<std::string, Path>  Paths;
 
       Paths paths_;
 
-      static std::string FormatTag(const std::string& tag)
-      {
-        if (tag.empty())
-        {
-          return tag;
-        }
-        else
-        {
-          std::string s;
-          s.reserve(tag.size());
-          s.push_back(tag[0]);
-
-          for (size_t i = 1; i < tag.size(); i++)
-          {
-            if (tag[i] == ' ')
-            {
-              s.push_back('-');
-            }
-            else if (isupper(tag[i]) &&
-                     tag[i - 1] == ' ')
-            {
-              s.push_back(tolower(tag[i]));
-            }
-            else
-            {
-              s.push_back(tag[i]);
-            }
-          }
-
-          return s;
-        }
-      }
-
-      std::string FormatUrl(const std::string& openApiUrl,
-                            bool hasMethod,
-                            const std::string& tag,
-                            const std::string& uri,
-                            const std::string& method) const
-      {
-        if (hasMethod)
-        {
-          std::string title;
-          Toolbox::ToUpperCase(title, method);
-          
-          if (openApiUrl.empty())
-          {
-            return title;
-          }
-          else
-          {
-            std::string p = uri;
-            boost::replace_all(p, "/", "~1");
-            
-            return ("`" + title + " <" + openApiUrl + "#tag/" +
-                    FormatTag(tag) + "/paths/" + p + "/" + method + ">`__");
-          }
-        }
-        else
-        {
-          return "";
-        }
-      }
-
     protected:
       virtual bool HandleCall(RestApiCall& call,
                               const std::set<std::string> uriArgumentsNames) ORTHANC_OVERRIDE
       {
         Path& path = paths_[ Toolbox::FlattenUri(call.GetFullUri()) ];
 
-        path.AddMethod(call.GetMethod());
+        path.AddMethod(call.GetMethod(), call.GetDocumentation().GetTag());
 
         if (call.GetDocumentation().HasSummary())
         {
-          path.SetSummary(call.GetDocumentation().GetTag(), call.GetDocumentation().GetSummary(), call.GetMethod());
+          path.SetSummary(call.GetDocumentation().GetSummary(), call.GetMethod());
         }
         
         return true;
@@ -666,10 +691,10 @@
         for (Paths::const_iterator it = paths_.begin(); it != paths_.end(); ++it)
         {
           target += "``" + it->first + "``,";
-          target += FormatUrl(openApiUrl, it->second.HasGet(), it->second.GetTag(), it->first, "get") + ",";
-          target += FormatUrl(openApiUrl, it->second.HasPost(), it->second.GetTag(), it->first, "post") + ",";
-          target += FormatUrl(openApiUrl, it->second.HasDelete(), it->second.GetTag(), it->first, "delete") + ",";
-          target += FormatUrl(openApiUrl, it->second.HasPut(), it->second.GetTag(), it->first, "put") + ",";
+          target += it->second.Format(openApiUrl, HttpMethod_Get, it->first) + ",";
+          target += it->second.Format(openApiUrl, HttpMethod_Post, it->first) + ",";
+          target += it->second.Format(openApiUrl, HttpMethod_Delete, it->first) + ",";
+          target += it->second.Format(openApiUrl, HttpMethod_Put, it->first) + ",";
           target += it->second.GetSummary() + "\n";
         }        
       }
--- a/OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp	Mon Dec 28 11:57:48 2020 +0100
+++ b/OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp	Mon Dec 28 13:08:00 2020 +0100
@@ -210,7 +210,8 @@
   }
 
 
-  static const char* TypeToString(RestApiCallDocumentation::Type type)
+  static void TypeToSchema(Json::Value& target,
+                           RestApiCallDocumentation::Type type)
   {
     switch (type)
     {
@@ -219,20 +220,30 @@
 
       case RestApiCallDocumentation::Type_String:
       case RestApiCallDocumentation::Type_Text:
-        return "string";
+        target["type"] = "string";
+        return;
 
       case RestApiCallDocumentation::Type_Number:
-        return "number";
+        target["type"] = "number";
+        return;
 
       case RestApiCallDocumentation::Type_Boolean:
-        return "boolean";
+        target["type"] = "boolean";
+        return;
 
       case RestApiCallDocumentation::Type_JsonObject:
-        return "object";
+        target["type"] = "object";
+        return;
 
       case RestApiCallDocumentation::Type_JsonListOfStrings:
+        target["type"] = "array";
+        target["items"]["type"] = "string";
+        return;
+
       case RestApiCallDocumentation::Type_JsonListOfObjects:
-        return "array";
+        target["type"] = "array";
+        target["items"]["type"] = "object";
+        return;
         
       default:
         throw OrthancException(ErrorCode_ParameterOutOfRange);
@@ -290,7 +301,7 @@
                  field != requestFields_.end(); ++field)
             {
               Json::Value p = Json::objectValue;
-              p["type"] = TypeToString(field->second.GetType());
+              TypeToSchema(p, field->second.GetType());
               p["description"] = field->second.GetDescription();
               schema["properties"][field->first] = p;         
             }        
@@ -312,7 +323,7 @@
                field != answerFields_.end(); ++field)
           {
             Json::Value p = Json::objectValue;
-            p["type"] = TypeToString(field->second.GetType());
+            TypeToSchema(p, field->second.GetType());
             p["description"] = field->second.GetDescription();
             schema["properties"][field->first] = p;         
           }        
@@ -358,7 +369,7 @@
         p["name"] = it->first;
         p["in"] = "query";
         p["required"] = it->second.IsRequired();
-        p["schema"]["type"] = TypeToString(it->second.GetType());
+        TypeToSchema(p["schema"], it->second.GetType());
         p["description"] = it->second.GetDescription();
         parameters.append(p);         
       }
@@ -370,7 +381,7 @@
         p["name"] = it->first;
         p["in"] = "header";
         p["required"] = it->second.IsRequired();
-        p["schema"]["type"] = TypeToString(it->second.GetType());
+        TypeToSchema(p["schema"], it->second.GetType());
         p["description"] = it->second.GetDescription();
         parameters.append(p);         
       }
@@ -387,7 +398,7 @@
         p["name"] = it->first;
         p["in"] = "path";
         p["required"] = it->second.IsRequired();
-        p["schema"]["type"] = TypeToString(it->second.GetType());
+        TypeToSchema(p["schema"], it->second.GetType());
         p["description"] = it->second.GetDescription();
         parameters.append(p);         
       }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp	Mon Dec 28 11:57:48 2020 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp	Mon Dec 28 13:08:00 2020 +0100
@@ -156,6 +156,9 @@
         .SetAnswerField("ID", RestApiCallDocumentation::Type_String, "Orthanc identifier of the new instance")
         .SetAnswerField("Path", RestApiCallDocumentation::Type_String, "Path to the new instance in the REST API")
         .SetAnswerField("Status", RestApiCallDocumentation::Type_String, "Can be `Success`, `AlreadyStored`, `Failure`, or `FilteredOut` (removed by some `NewInstanceFilter`)")
+        .SetAnswerField("ParentPatient", RestApiCallDocumentation::Type_String, "Orthanc identifier of the parent patient")
+        .SetAnswerField("ParentStudy", RestApiCallDocumentation::Type_String, "Orthanc identifier of the parent study")
+        .SetAnswerField("ParentSeries", RestApiCallDocumentation::Type_String, "Orthanc identifier of the parent series")
         .SetSample(sample);
       return;
     }
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Mon Dec 28 11:57:48 2020 +0100
+++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp	Mon Dec 28 13:08:00 2020 +0100
@@ -1595,7 +1595,7 @@
         .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
         .SetSummary("Get size of attachment on disk")
         .SetDescription("Get the size of one attachment associated with the given " + r + ", as stored on the disk. "
-                        "This is different from `.../size` if `EnableStorage` is `true`.")
+                        "This is different from `.../size` iff `EnableStorage` is `true`.")
         .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
         .SetUriArgument("name", "The name of the attachment")
         .AddAnswerType(MimeType_PlainText, "The size of the attachment, as stored on the disk");
@@ -1645,7 +1645,7 @@
         .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
         .SetSummary("Get MD5 of attachment on disk")
         .SetDescription("Get the MD5 hash of one attachment associated with the given " + r + ", as stored on the disk. "
-                        "This is different from `.../md5` if `EnableStorage` is `true`.")
+                        "This is different from `.../md5` iff `EnableStorage` is `true`.")
         .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
         .SetUriArgument("name", "The name of the attachment")
         .AddAnswerType(MimeType_PlainText, "The MD5 of the attachment, as stored on the disk");
@@ -1752,6 +1752,19 @@
 
   static void DeleteAttachment(RestApiDeleteCall& call)
   {
+    if (call.IsDocumentation())
+    {
+      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
+      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      call.GetDocumentation()
+        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetSummary("Delete attachment")
+        .SetDescription("Delete an attachment associated with the given DICOM " + r)
+        .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
+        .SetUriArgument("name", "The name of the attachment");
+      return;
+    }
+
     CheckValidResourceType(call);
 
     std::string publicId = call.GetUriComponent("id", "");
@@ -1796,6 +1809,19 @@
   template <enum CompressionType compression>
   static void ChangeAttachmentCompression(RestApiPostCall& call)
   {
+    if (call.IsDocumentation())
+    {
+      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
+      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      call.GetDocumentation()
+        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetSummary(compression == CompressionType_None ? "Uncompress attachment" : "Compress attachment")
+        .SetDescription("Change the compression scheme that is used to store an attachment.")
+        .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
+        .SetUriArgument("name", "The name of the attachment");
+      return;
+    }
+
     CheckValidResourceType(call);
 
     std::string publicId = call.GetUriComponent("id", "");
@@ -1809,6 +1835,20 @@
 
   static void IsAttachmentCompressed(RestApiGetCall& call)
   {
+    if (call.IsDocumentation())
+    {
+      ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str());
+      std::string r = GetResourceTypeText(t, false /* plural */, false /* upper case */);
+      call.GetDocumentation()
+        .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */))
+        .SetSummary("Is attachment compressed?")
+        .SetDescription("Test whether the attachment has been stored as a compressed file on the disk.")
+        .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest")
+        .SetUriArgument("name", "The name of the attachment")
+        .AddAnswerType(MimeType_PlainText, "`0` if the attachment was stored uncompressed, `1` if it was compressed");
+      return;
+    }
+
     FileInfo info;
     if (GetAttachmentInfo(info, call))
     {
--- a/OrthancServer/Sources/main.cpp	Mon Dec 28 11:57:48 2020 +0100
+++ b/OrthancServer/Sources/main.cpp	Mon Dec 28 13:08:00 2020 +0100
@@ -1809,8 +1809,7 @@
           MemoryStorageArea inMemoryStorage;
           ServerContext context(inMemoryDatabase, inMemoryStorage, true /* unit testing */, 0 /* max completed jobs */);
           OrthancRestApi restApi(context, false /* no Orthanc Explorer */);
-          //restApi.GenerateReStructuredTextCheatSheet(cheatsheet, "https://api.orthanc-server.com/index.html");
-          restApi.GenerateReStructuredTextCheatSheet(cheatsheet, "http://localhost:8000/a.html");
+          restApi.GenerateReStructuredTextCheatSheet(cheatsheet, "https://api.orthanc-server.com/index.html");
           context.Stop();
         }