# HG changeset patch # User Sebastien Jodogne # Date 1609157280 -3600 # Node ID 22a1352a082350ec16d8b338582c980097c97859 # Parent 68b96234fbd634843487d40d0d1f2d84641f16bd cont openapi diff -r 68b96234fbd6 -r 22a1352a0823 OrthancFramework/Sources/RestApi/RestApi.cpp --- 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 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 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"; } } diff -r 68b96234fbd6 -r 22a1352a0823 OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp --- 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); } diff -r 68b96234fbd6 -r 22a1352a0823 OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp --- 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; } diff -r 68b96234fbd6 -r 22a1352a0823 OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp --- 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 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)) { diff -r 68b96234fbd6 -r 22a1352a0823 OrthancServer/Sources/main.cpp --- 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(); }