# HG changeset patch # User Sebastien Jodogne # Date 1609251720 -3600 # Node ID a7d72378e1cbf592160b32a4958c9f9b0814b725 # Parent f95ad769e6717f2a07dbdb7e69700e4f2164e918 cont openapi diff -r f95ad769e671 -r a7d72378e1cb OrthancFramework/Sources/RestApi/RestApi.cpp --- a/OrthancFramework/Sources/RestApi/RestApi.cpp Tue Dec 29 12:21:51 2020 +0100 +++ b/OrthancFramework/Sources/RestApi/RestApi.cpp Tue Dec 29 15:22:00 2020 +0100 @@ -195,11 +195,13 @@ ok = (resource.Handle(call) && HandleCall(call, uriArgumentsNames)); } - catch (OrthancException&) + catch (OrthancException& e) { + LOG(ERROR) << "Exception while documenting GET " << path << ": " << e.What(); } catch (boost::bad_lexical_cast&) { + LOG(ERROR) << "Bad lexical cast while documenting GET " << path; } if (ok) @@ -231,11 +233,13 @@ ok = (resource.Handle(call) && HandleCall(call, uriArgumentsNames)); } - catch (OrthancException&) + catch (OrthancException& e) { + LOG(ERROR) << "Exception while documenting POST " << path << ": " << e.What(); } catch (boost::bad_lexical_cast&) { + LOG(ERROR) << "Bad lexical cast while documenting POST " << path; } if (ok) @@ -266,11 +270,13 @@ ok = (resource.Handle(call) && HandleCall(call, uriArgumentsNames)); } - catch (OrthancException&) + catch (OrthancException& e) { + LOG(ERROR) << "Exception while documenting DELETE " << path << ": " << e.What(); } catch (boost::bad_lexical_cast&) { + LOG(ERROR) << "Bad lexical cast while documenting DELETE " << path; } if (ok) @@ -302,11 +308,13 @@ ok = (resource.Handle(call) && HandleCall(call, uriArgumentsNames)); } - catch (OrthancException&) + catch (OrthancException& e) { + LOG(ERROR) << "Exception while documenting PUT " << path << ": " << e.What(); } catch (boost::bad_lexical_cast&) { + LOG(ERROR) << "Bad lexical cast while documenting PUT " << path; } if (ok) diff -r f95ad769e671 -r a7d72378e1cb OrthancFramework/Sources/RestApi/RestApiCallDocumentation.h --- a/OrthancFramework/Sources/RestApi/RestApiCallDocumentation.h Tue Dec 29 12:21:51 2020 +0100 +++ b/OrthancFramework/Sources/RestApi/RestApiCallDocumentation.h Tue Dec 29 15:22:00 2020 +0100 @@ -202,9 +202,10 @@ return tag_; } - void SetDeprecated() + RestApiCallDocumentation& SetDeprecated() { deprecated_ = true; + return *this; } bool IsDeprecated() const diff -r f95ad769e671 -r a7d72378e1cb OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Tue Dec 29 12:21:51 2020 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestModalities.cpp Tue Dec 29 15:22:00 2020 +0100 @@ -110,6 +110,40 @@ } + static void DocumentModalityParametersShared(RestApiCall& call, + bool includePermissions) + { + call.GetDocumentation() + .SetRequestField("AET", RestApiCallDocumentation::Type_String, + "AET of the remote DICOM modality", true) + .SetRequestField("Host", RestApiCallDocumentation::Type_String, + "Host address of the remote DICOM modality (typically, an IP address)", true) + .SetRequestField("Port", RestApiCallDocumentation::Type_Number, + "TCP port of the remote DICOM modality", true) + .SetRequestField("Manufacturer", RestApiCallDocumentation::Type_String, "Manufacturer of the remote DICOM " + "modality (check configuration option `DicomModalities` for possible values", false); + + if (includePermissions) + { + call.GetDocumentation() + .SetRequestField("AllowEcho", RestApiCallDocumentation::Type_Boolean, + "Whether to accept C-ECHO SCU commands issued by the remote modality", false) + .SetRequestField("AllowStore", RestApiCallDocumentation::Type_Boolean, + "Whether to accept C-STORE SCU commands issued by the remote modality", false) + .SetRequestField("AllowFind", RestApiCallDocumentation::Type_Boolean, + "Whether to accept C-FIND SCU commands issued by the remote modality", false) + .SetRequestField("AllowMove", RestApiCallDocumentation::Type_Boolean, + "Whether to accept C-MOVE SCU commands issued by the remote modality", false) + .SetRequestField("AllowGet", RestApiCallDocumentation::Type_Boolean, + "Whether to accept C-GET SCU commands issued by the remote modality", false) + .SetRequestField("AllowStorageCommitment", RestApiCallDocumentation::Type_Boolean, + "Whether to accept storage commitment requests issued by the remote modality", false) + .SetRequestField("AllowTranscoding", RestApiCallDocumentation::Type_Boolean, + "Whether to allow transcoding for operations initiated by this modality (typically, C-GET)", false); + } + } + + /*************************************************************************** * DICOM C-Echo SCU ***************************************************************************/ @@ -158,10 +192,34 @@ output.SignalError(HttpStatus_500_InternalServerError); } } + + + static void DocumentEchoShared(RestApiPostCall& call) + { + call.GetDocumentation() + .SetRequestField(KEY_TIMEOUT, RestApiCallDocumentation::Type_Number, + "Timeout for the C-ECHO command, in seconds", false) + .SetRequestField(KEY_CHECK_FIND, RestApiCallDocumentation::Type_Boolean, + "Issue a dummy C-FIND command after the C-GET SCU, in order to check whether the remote " + "modality knows about Orthanc. This field defaults to the value of the `DicomEchoChecksFind` " + "configuration option. New in Orthanc 1.8.1.", false); + } static void DicomEcho(RestApiPostCall& call) { + if (call.IsDocumentation()) + { + DocumentEchoShared(call); + call.GetDocumentation() + .SetTag("Networking") + .SetSummary("Trigger C-ECHO SCU") + .SetDescription("Trigger C-ECHO SCU command against the DICOM modality whose identifier is provided in URL: " + "https://book.orthanc-server.com/users/rest.html#performing-c-echo") + .SetUriArgument("id", "Identifier of the modality of interest"); + return; + } + Json::Value body = Json::objectValue; if (call.GetBodySize() == 0 /* allow empty body, was disallowed in Orthanc 1.7.0->1.8.1 */ || @@ -179,6 +237,18 @@ static void DicomEchoTool(RestApiPostCall& call) { + if (call.IsDocumentation()) + { + DocumentEchoShared(call); + DocumentModalityParametersShared(call, false); + call.GetDocumentation() + .SetTag("System") + .SetSummary("Trigger C-ECHO SCU") + .SetDescription("Trigger C-ECHO SCU command against a DICOM modality described in the POST body, " + "without having to register the modality in some `/modalities/{id}` (new in Orthanc 1.8.1)"); + return; + } + Json::Value body; if (call.ParseJsonRequest(body)) { @@ -286,6 +356,20 @@ static void DicomFindPatient(RestApiPostCall& call) { + if (call.IsDocumentation()) + { + call.GetDocumentation() + .SetDeprecated() + .SetTag("Networking") + .SetSummary("C-FIND SCU for patients") + .SetDescription("Trigger C-FIND SCU command against the DICOM modality whose identifier is provided in URL, " + "in order to find a patient. Deprecated in favor of `/modalities/{id}/query`.") + .AddRequestType(MimeType_Json, "Associative array containing the query on the values of the DICOM tags") + .AddAnswerType(MimeType_Json, "JSON array describing the DICOM tags of the matching patients") + .SetUriArgument("id", "Identifier of the modality of interest"); + return; + } + LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); DicomMap fields; @@ -309,6 +393,20 @@ static void DicomFindStudy(RestApiPostCall& call) { + if (call.IsDocumentation()) + { + call.GetDocumentation() + .SetDeprecated() + .SetTag("Networking") + .SetSummary("C-FIND SCU for studies") + .SetDescription("Trigger C-FIND SCU command against the DICOM modality whose identifier is provided in URL, " + "in order to find a study. Deprecated in favor of `/modalities/{id}/query`.") + .AddRequestType(MimeType_Json, "Associative array containing the query on the values of the DICOM tags") + .AddAnswerType(MimeType_Json, "JSON array describing the DICOM tags of the matching studies") + .SetUriArgument("id", "Identifier of the modality of interest"); + return; + } + LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); DicomMap fields; @@ -338,6 +436,20 @@ static void DicomFindSeries(RestApiPostCall& call) { + if (call.IsDocumentation()) + { + call.GetDocumentation() + .SetDeprecated() + .SetTag("Networking") + .SetSummary("C-FIND SCU for series") + .SetDescription("Trigger C-FIND SCU command against the DICOM modality whose identifier is provided in URL, " + "in order to find a series. Deprecated in favor of `/modalities/{id}/query`.") + .AddRequestType(MimeType_Json, "Associative array containing the query on the values of the DICOM tags") + .AddAnswerType(MimeType_Json, "JSON array describing the DICOM tags of the matching series") + .SetUriArgument("id", "Identifier of the modality of interest"); + return; + } + LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); DicomMap fields; @@ -368,6 +480,20 @@ static void DicomFindInstance(RestApiPostCall& call) { + if (call.IsDocumentation()) + { + call.GetDocumentation() + .SetDeprecated() + .SetTag("Networking") + .SetSummary("C-FIND SCU for instances") + .SetDescription("Trigger C-FIND SCU command against the DICOM modality whose identifier is provided in URL, " + "in order to find an instance. Deprecated in favor of `/modalities/{id}/query`.") + .AddRequestType(MimeType_Json, "Associative array containing the query on the values of the DICOM tags") + .AddAnswerType(MimeType_Json, "JSON array describing the DICOM tags of the matching instances") + .SetUriArgument("id", "Identifier of the modality of interest"); + return; + } + LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); DicomMap fields; @@ -412,6 +538,22 @@ static void DicomFind(RestApiPostCall& call) { + if (call.IsDocumentation()) + { + call.GetDocumentation() + .SetDeprecated() + .SetTag("Networking") + .SetSummary("Hierarchical C-FIND SCU") + .SetDescription("Trigger a sequence of C-FIND SCU commands against the DICOM modality whose identifier is provided in URL, " + "in order to discover a hierarchy of matching patients/studies/series. " + "Deprecated in favor of `/modalities/{id}/query`.") + .AddRequestType(MimeType_Json, "Associative array containing the query on the values of the DICOM tags") + .AddAnswerType(MimeType_Json, "JSON array describing the DICOM tags of the matching patients, embedding the " + "matching studies, then the matching series.") + .SetUriArgument("id", "Identifier of the modality of interest"); + return; + } + LOG(WARNING) << "This URI is deprecated: " << call.FlattenUri(); DicomMap m; @@ -511,6 +653,28 @@ static void DicomQuery(RestApiPostCall& call) { + if (call.IsDocumentation()) + { + call.GetDocumentation() + .SetTag("Networking") + .SetSummary("Trigger C-FIND SCU") + .SetDescription("Trigger C-FIND SCU command against the DICOM modality whose identifier is provided in URL: " + "https://book.orthanc-server.com/users/rest.html#performing-query-retrieve-c-find-and-find-with-rest") + .SetUriArgument("id", "Identifier of the modality of interest") + .SetRequestField(KEY_QUERY, RestApiCallDocumentation::Type_JsonObject, + "Associative array containing the filter on the values of the DICOM tags", true) + .SetRequestField(KEY_LEVEL, RestApiCallDocumentation::Type_String, + "Level of the query (`Patient`, `Study`, `Series` or `Instance`)", true) + .SetRequestField(KEY_NORMALIZE, RestApiCallDocumentation::Type_Boolean, + "Whether to normalize the query, i.e. whether to wipe out from the query, the DICOM tags " + "that are not applicable for the query-retrieve level of interest", false) + .SetAnswerField("ID", RestApiCallDocumentation::Type_JsonObject, + "Identifier of the query, to be used with `/queries/{id}`") + .SetAnswerField("Path", RestApiCallDocumentation::Type_JsonObject, + "Root path to the query in the REST API"); + return; + } + ServerContext& context = OrthancRestApi::GetContext(call); Json::Value request; @@ -791,7 +955,7 @@ "AET of the target modality. By default, the AET of Orthanc is used, as defined in the " "`DicomAet` configuration option.", false) .SetRequestField(KEY_TIMEOUT, RestApiCallDocumentation::Type_Number, - "Timeout for the C-MOVE command, in seconds.", false) + "Timeout for the C-MOVE command, in seconds", false) .AddRequestType(MimeType_PlainText, "AET of the target modality"); } @@ -996,7 +1160,7 @@ .SetUriArgument("id", "Identifier of the query of interest") .SetUriArgument("index", "Index of the answer") .SetRequestField(KEY_QUERY, RestApiCallDocumentation::Type_JsonObject, - "Filter on the DICOM tags", false) + "Associative array containing the filter on the values of the DICOM tags", true) .SetAnswerField("ID", RestApiCallDocumentation::Type_JsonObject, "Identifier of the query, to be used with `/queries/{id}`") .SetAnswerField("Path", RestApiCallDocumentation::Type_JsonObject, @@ -1244,6 +1408,23 @@ static void DicomStoreStraight(RestApiPostCall& call) { + if (call.IsDocumentation()) + { + call.GetDocumentation() + .SetTag("Networking") + .SetSummary("Straight C-STORE SCU") + .SetDescription("Synchronously send the DICOM instance in the POST body to the remote DICOM modality " + "whose identifier is provided in URL, without having to first store it locally within Orthanc. " + "This is an alternative to command-line tools such as `storescu` from DCMTK or dcm4che.") + .SetUriArgument("id", "Identifier of the modality of interest") + .AddRequestType(MimeType_Dicom, "DICOM instance to be sent") + .SetAnswerField(SOP_CLASS_UID, RestApiCallDocumentation::Type_String, + "SOP class UID of the DICOM instance, if the C-STORE SCU has succeeded") + .SetAnswerField(SOP_INSTANCE_UID, RestApiCallDocumentation::Type_String, + "SOP instance UID of the DICOM instance, if the C-STORE SCU has succeeded"); + return; + } + Json::Value body = Json::objectValue; // No body DicomStoreUserConnection connection(GetAssociationParameters(call, body)); @@ -1495,7 +1676,7 @@ call.GetDocumentation() .SetTag("Networking") .SetSummary("Get peer configuration") - .SetDescription("Get detailed information about the configuration of some Orthanc peer.") + .SetDescription("Get detailed information about the configuration of some Orthanc peer") .SetUriArgument("id", "Identifier of the peer of interest") .AddAnswerType(MimeType_Json, "Configuration of the peer") .SetSample(sample); @@ -1604,6 +1785,18 @@ static void UpdateModality(RestApiPutCall& call) { + if (call.IsDocumentation()) + { + DocumentModalityParametersShared(call, true); + call.GetDocumentation() + .SetTag("Networking") + .SetSummary("Update DICOM modality") + .SetDescription("Define a new DICOM modality, or update an existing one. This change is permanent iff. " + "`DicomModalitiesInDatabase` is `true`, otherwise it is lost at the next restart of Orthanc.") + .SetUriArgument("id", "Identifier of the new/updated DICOM modality"); + return; + } + ServerContext& context = OrthancRestApi::GetContext(call); Json::Value json; @@ -1674,7 +1867,7 @@ call.GetDocumentation() .SetTag("Networking") .SetSummary("Get modality configuration") - .SetDescription("Get detailed information about the configuration of some DICOM modality.") + .SetDescription("Get detailed information about the configuration of some DICOM modality") .SetUriArgument("id", "Identifier of the modality of interest") .AddAnswerType(MimeType_Json, "Configuration of the modality") .SetSample(sample); @@ -1696,6 +1889,31 @@ static void UpdatePeer(RestApiPutCall& call) { + if (call.IsDocumentation()) + { + call.GetDocumentation() + .SetTag("Networking") + .SetSummary("Update Orthanc peer") + .SetDescription("Define a new Orthanc peer, or update an existing one. This change is permanent iff. " + "`OrthancPeersInDatabase` is `true`, otherwise it is lost at the next restart of Orthanc.") + .SetUriArgument("id", "Identifier of the new/updated Orthanc peer") + .SetRequestField("URL", RestApiCallDocumentation::Type_String, + "URL of the root of the REST API of the remote Orthanc peer, for instance `http://localhost:8042/`", true) + .SetRequestField("Username", RestApiCallDocumentation::Type_String, + "Username for the credentials", false) + .SetRequestField("Password", RestApiCallDocumentation::Type_String, + "Password for the credentials", false) + .SetRequestField("CertificateFile", RestApiCallDocumentation::Type_String, + "SSL certificate for the HTTPS connections", false) + .SetRequestField("CertificateKeyFile", RestApiCallDocumentation::Type_String, + "Key file for the SSL certificate for the HTTPS connections", false) + .SetRequestField("CertificateKeyPassword", RestApiCallDocumentation::Type_String, + "Key password for the SSL certificate for the HTTPS connections", false) + .SetRequestField("HttpHeaders", RestApiCallDocumentation::Type_JsonObject, + "HTTP headers to be used for the connections to the remote peer", false); + return; + } + ServerContext& context = OrthancRestApi::GetContext(call); Json::Value json; @@ -1744,6 +1962,19 @@ static void DicomFindWorklist(RestApiPostCall& call) { + if (call.IsDocumentation()) + { + call.GetDocumentation() + .SetTag("Networking") + .SetSummary("C-FIND SCU for worklist") + .SetDescription("Trigger C-FIND SCU command against the remote worklists of the DICOM modality " + "whose identifier is provided in URL") + .SetUriArgument("id", "Identifier of the modality of interest") + .AddRequestType(MimeType_Json, "Associative array containing the query on the values of the DICOM tags") + .AddAnswerType(MimeType_Json, "JSON array describing the DICOM tags of the matching worklists"); + return; + } + Json::Value json; if (call.ParseJsonRequest(json)) { diff -r f95ad769e671 -r a7d72378e1cb OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp --- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Tue Dec 29 12:21:51 2020 +0100 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Tue Dec 29 15:22:00 2020 +0100 @@ -408,6 +408,17 @@ static void ExportInstanceFile(RestApiPostCall& call) { + if (call.IsDocumentation()) + { + call.GetDocumentation() + .SetTag("Instances") + .SetSummary("Write DICOM onto filesystem") + .SetDescription("Write the DICOM file onto the filesystem where Orthanc is running") + .SetUriArgument("id", "Orthanc identifier of the DICOM instance of interest") + .AddRequestType(MimeType_PlainText, "Target path on the filesystem"); + return; + } + ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); @@ -2363,6 +2374,31 @@ static const char* const KEY_QUERY = "Query"; static const char* const KEY_SINCE = "Since"; + if (call.IsDocumentation()) + { + call.GetDocumentation() + .SetTag("System") + .SetSummary("Look for local resources") + .SetDescription("This URI can be used to perform a search on the content of the local Orthanc server, " + "in a way that is similar to querying remote DICOM modalities using C-FIND SCU: " + "https://book.orthanc-server.com/users/rest.html#performing-finds-within-orthanc") + .SetRequestField(KEY_CASE_SENSITIVE, RestApiCallDocumentation::Type_Boolean, + "Enable case-sensitive search for PN value representations (defaults to configuration option `CaseSensitivePN`)", false) + .SetRequestField(KEY_EXPAND, RestApiCallDocumentation::Type_Boolean, + "Also retrieve the content of the matching resources, not only their Orthanc identifiers", false) + .SetRequestField(KEY_LEVEL, RestApiCallDocumentation::Type_String, + "Level of the query (`Patient`, `Study`, `Series` or `Instance`)", true) + .SetRequestField(KEY_LIMIT, RestApiCallDocumentation::Type_Number, + "Limit the number of reported resources", false) + .SetRequestField(KEY_SINCE, RestApiCallDocumentation::Type_Number, + "Show only the resources since the provided index (in conjunction with `Limit`)", false) + .SetRequestField(KEY_QUERY, RestApiCallDocumentation::Type_JsonObject, + "Associative array containing the filter on the values of the DICOM tags", true) + .AddAnswerType(MimeType_Json, "JSON array containing either the Orthanc identifiers, or detailed information " + "about the reported resources (if `Expand` argument is `true`)"); + return; + } + ServerContext& context = OrthancRestApi::GetContext(call); Json::Value request; @@ -2668,6 +2704,28 @@ static void OrderSlices(RestApiGetCall& call) { + if (call.IsDocumentation()) + { + call.GetDocumentation() + .SetDeprecated() + .SetTag("Series") + .SetSummary("Order the slices") + .SetDescription("Sort the instances and frames (slices) of the DICOM series whose Orthanc identifier is provided in the URL. " + "This URI is essentially used by the Orthanc Web viewer and by the Osimis Web viewer.") + .SetUriArgument("id", "Orthanc identifier of the series of interest") + .SetAnswerField("Dicom", RestApiCallDocumentation::Type_JsonListOfStrings, + "Ordered list of paths to DICOM instances") + .SetAnswerField("Slices", RestApiCallDocumentation::Type_JsonListOfStrings, + "Ordered list of paths to frames. It is recommended to use this field, as it is also valid " + "in the case of multiframe images.") + .SetAnswerField("SlicesShort", RestApiCallDocumentation::Type_JsonListOfObjects, + "Same information as the `Slices` field, but in a compact form") + .SetAnswerField("Type", RestApiCallDocumentation::Type_String, + "Can be `Volume` (for 3D volumes) or `Sequence` (notably for cine images)") + .SetTruncatedJsonHttpGetSample("https://demo.orthanc-server.com/series/1e2c125c-411b8e86-3f4fe68e-a7584dd3-c6da78f0/ordered-slices", 10); + return; + } + const std::string id = call.GetUriComponent("id", ""); ServerIndex& index = OrthancRestApi::GetIndex(call);