# HG changeset patch # User Sebastien Jodogne # Date 1608744733 -3600 # Node ID ad646ff506d03f42d048a2a091da96bb588442fb # Parent b651989194d3af3123987cfe1908164ef94040a1 cont openapi diff -r b651989194d3 -r ad646ff506d0 OrthancFramework/Sources/RestApi/RestApi.cpp --- 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(); } } diff -r b651989194d3 -r ad646ff506d0 OrthancFramework/Sources/RestApi/RestApiCallDocumentation.cpp --- 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); } diff -r b651989194d3 -r ad646ff506d0 OrthancFramework/Sources/RestApi/RestApiCallDocumentation.h --- 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 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, diff -r b651989194d3 -r ad646ff506d0 OrthancServer/Sources/OrthancRestApi/OrthancRestApi.cpp --- 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, " diff -r b651989194d3 -r ad646ff506d0 OrthancServer/Sources/OrthancRestApi/OrthancRestChanges.cpp --- 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); } diff -r b651989194d3 -r ad646ff506d0 OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp --- 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; } diff -r b651989194d3 -r ad646ff506d0 OrthancServer/Sources/OrthancRestApi/OrthancRestSystem.cpp --- 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; } diff -r b651989194d3 -r ad646ff506d0 OrthancServer/Sources/main.cpp --- 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);