Mercurial > hg > orthanc
changeset 6411:98964246e23e
ErrorPayload in OrthancException
line wrap: on
line diff
--- a/NEWS Thu Nov 13 06:32:48 2025 +0100 +++ b/NEWS Fri Nov 14 15:12:16 2025 +0100 @@ -13,12 +13,11 @@ * Fix: C-Get SCU jobs or HTTP responses were always successful even if the C-Get operation actually failed. -* C-Store, C-Move and C-Get jobs and HTTP responses now include a new "DimseErrorStatus" - field (ONLY if the operation fails). +* When an error occurs while Orthanc handles an HTTP request, it may now include a new + "ErrorPayload" in the HTTP response with details about the failed operation. + This is currently implemented for C-Store, C-Move and C-Get operations. * C-Move and C-Get jobs and HTTP responses now include a new "Details" field with the "DimseErrorStatus" - of each operation and the list of "RetrievedInstancesIds". Note that, if you have multiple - resources to retrieve and one of them fails, you'll only get those details if you have set - "Permissive" to true to allow other operations to continue after the failure. + of each operation and the list of "RetrievedInstancesIds". Maintenance -----------
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Resources/CMake/OrthancFrameworkConfiguration.cmake Fri Nov 14 15:12:16 2025 +0100 @@ -574,6 +574,7 @@ ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DicomControlUserConnection.cpp ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DicomServer.cpp ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DicomStoreUserConnection.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/DimseErrorPayload.cpp ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/Internals/CommandDispatcher.cpp ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/Internals/FindScp.cpp ${CMAKE_CURRENT_LIST_DIR}/../../Sources/DicomNetworking/Internals/MoveScp.cpp
--- a/OrthancFramework/Sources/DicomFormat/DicomMap.cpp Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/DicomFormat/DicomMap.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -255,7 +255,7 @@ if (existingLevelTags.find(tag) != existingLevelTags.end()) { - throw OrthancException(ErrorCode_MainDicomTagsMultiplyDefined, tag.Format() + " is already defined", false); + throw OrthancException(ErrorCode_MainDicomTagsMultiplyDefined, tag.Format() + " is already defined", LogException_No); } if (level == ResourceType_Study) // all patients main dicom tags are also copied at study level @@ -264,7 +264,7 @@ if (patientLevelTags.find(tag) != patientLevelTags.end()) { - throw OrthancException(ErrorCode_MainDicomTagsMultiplyDefined, tag.Format() + " is already defined", false); + throw OrthancException(ErrorCode_MainDicomTagsMultiplyDefined, tag.Format() + " is already defined", LogException_No); } }
--- a/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomControlUserConnection.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -24,6 +24,7 @@ #include "../PrecompiledHeaders.h" #include "DicomControlUserConnection.h" +#include "DimseErrorPayload.h" #include "../Compatibility.h" #include "../DicomFormat/DicomArray.h" @@ -393,13 +394,15 @@ "C-FIND SCU to AET \"" + parameters_.GetRemoteModality().GetApplicationEntityTitle() + "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus) + - " (unable to process - invalid query ?)"); + " (unable to process - invalid query ?)", + MakeDimseErrorStatusPayload(response.DimseStatus)); } else { throw OrthancException(ErrorCode_NetworkProtocol, "C-FIND SCU to AET \"" + parameters_.GetRemoteModality().GetApplicationEntityTitle() + - "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus)); + "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus), + MakeDimseErrorStatusPayload(response.DimseStatus)); } } } @@ -522,14 +525,14 @@ parameters_.GetRemoteModality().GetApplicationEntityTitle() + "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus) + " (unable to process - resource not found ?)", - response.DimseStatus); + MakeDimseErrorStatusPayload(response.DimseStatus)); } else { throw OrthancException(ErrorCode_NetworkProtocol, "C-MOVE SCU to AET \"" + parameters_.GetRemoteModality().GetApplicationEntityTitle() + "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus), - response.DimseStatus); + MakeDimseErrorStatusPayload(response.DimseStatus)); } } } @@ -665,7 +668,7 @@ "C-GET SCU to AET \"" + parameters_.GetRemoteModality().GetApplicationEntityTitle() + "\" has failed with DIMSE status " + DimseToHexString(rsp.msg.CGetRSP.DimseStatus), - rsp.msg.CGetRSP.DimseStatus); + MakeDimseErrorStatusPayload(rsp.msg.CGetRSP.DimseStatus)); } } // Handle C-STORE Request
--- a/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -24,6 +24,7 @@ #include "../PrecompiledHeaders.h" #include "DicomStoreUserConnection.h" +#include "DimseErrorPayload.h" #include "../DicomParsing/FromDcmtkBridge.h" #include "../DicomParsing/ParsedDicomFile.h" @@ -464,7 +465,7 @@ "C-STORE SCU to AET \"" + GetParameters().GetRemoteModality().GetApplicationEntityTitle() + "\" has failed with DIMSE status " + DimseToHexString(response.DimseStatus), - response.DimseStatus); + MakeDimseErrorStatusPayload(response.DimseStatus)); } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancFramework/Sources/DicomNetworking/DimseErrorPayload.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -0,0 +1,57 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/>. + **/ + + +#include "../PrecompiledHeaders.h" +#include "DimseErrorPayload.h" +#include <assert.h> + +namespace Orthanc +{ + static const char* const DIMSE_ERROR_PAYLOAD_TYPE = "DimseErrorPayload"; + static const char* const DIMSE_ERROR_STATUS = "DimseErrorStatus"; // uint16_t + + Json::Value MakeDimseErrorStatusPayload(uint16_t dimseErrorStatus) + { + Json::Value payload; + payload[ERROR_PAYLOAD_TYPE] = DIMSE_ERROR_PAYLOAD_TYPE; + payload[DIMSE_ERROR_STATUS] = dimseErrorStatus; + return payload; + } + + bool IsDimseErrorStatusPayload(const Json::Value& payload) + { + assert(payload.isMember(ERROR_PAYLOAD_TYPE)); + + return payload[ERROR_PAYLOAD_TYPE].asString() == DIMSE_ERROR_PAYLOAD_TYPE; + } + + uint16_t GetDimseErrorStatusFromPayload(const Json::Value& payload) + { + assert(IsDimseErrorStatusPayload(payload)); + assert(payload.isMember(DIMSE_ERROR_STATUS)); + + return static_cast<uint16_t>(payload[DIMSE_ERROR_STATUS].asUInt()); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/OrthancFramework/Sources/DicomNetworking/DimseErrorPayload.h Fri Nov 14 15:12:16 2025 +0100 @@ -0,0 +1,42 @@ +/** + * Orthanc - A Lightweight, RESTful DICOM Store + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * Copyright (C) 2017-2023 Osimis S.A., Belgium + * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium + * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/>. + **/ + + +#pragma once + +#if ORTHANC_ENABLE_DCMTK_NETWORKING != 1 +# error The macro ORTHANC_ENABLE_DCMTK_NETWORKING must be set to 1 +#endif + +#include "../OrthancException.h" + + +namespace Orthanc +{ + Json::Value MakeDimseErrorStatusPayload(uint16_t dimseErrorStatus); + + bool IsDimseErrorStatusPayload(const Json::Value& payload); + + uint16_t GetDimseErrorStatusFromPayload(const Json::Value& payload); + +}
--- a/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -1409,7 +1409,7 @@ } CLOG(INFO, DICOM) << "Unknown DICOM tag: \"" << name << "\""; - throw OrthancException(ErrorCode_UnknownDicomTag, name, false); + throw OrthancException(ErrorCode_UnknownDicomTag, name, LogException_No); } #endif }
--- a/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -966,12 +966,12 @@ { throw OrthancException(ErrorCode_NotImplemented, "The built-in DCMTK decoder cannot decode some DICOM instance " - "whose transfer syntax is: " + std::string(GetTransferSyntaxUid(s)), false /* don't log here*/); + "whose transfer syntax is: " + std::string(GetTransferSyntaxUid(s)), LogException_No); } else { throw OrthancException(ErrorCode_NotImplemented, - "The built-in DCMTK decoder cannot decode some DICOM instance", false /* don't log here*/); + "The built-in DCMTK decoder cannot decode some DICOM instance", LogException_No); } }
--- a/OrthancFramework/Sources/JobsEngine/IJob.h Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/JobsEngine/IJob.h Fri Nov 14 15:12:16 2025 +0100 @@ -81,5 +81,7 @@ virtual bool GetUserData(Json::Value& userData) const = 0; virtual void SetUserData(const Json::Value& userData) = 0; + + virtual bool LookupErrorPayload(Json::Value& payload) const = 0; }; }
--- a/OrthancFramework/Sources/JobsEngine/JobInfo.cpp Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/JobsEngine/JobInfo.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -196,9 +196,9 @@ target["UserData"] = status_.GetUserData(); } - if (status_.HasDimseErrorStatus()) + if (status_.HasErrorPayload()) { - target["DimseErrorStatus"] = status_.GetDimseErrorStatus(); + target["ErrorPayload"] = status_.GetErrorPayload(); } target["Type"] = status_.GetJobType();
--- a/OrthancFramework/Sources/JobsEngine/JobStatus.cpp Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/JobsEngine/JobStatus.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -34,9 +34,7 @@ progress_(0), jobType_("Invalid"), publicContent_(Json::objectValue), - hasSerialized_(false), - hasDimseErrorStatus_(false), - dimseErrorStatus_(0x0000) + hasSerialized_(false) { } @@ -62,14 +60,13 @@ JobStatus::JobStatus(ErrorCode code, const std::string& details, const IJob& job, - uint16_t dimseErrorStatus) : + const Json::Value& errorPayload) : errorCode_(code), progress_(job.GetProgress()), publicContent_(Json::objectValue), hasSerialized_(false), details_(details), - hasDimseErrorStatus_(true), - dimseErrorStatus_(dimseErrorStatus) + errorPayload_(errorPayload) { InitInternal(job); } @@ -82,9 +79,7 @@ progress_(job.GetProgress()), publicContent_(Json::objectValue), hasSerialized_(false), - details_(details), - hasDimseErrorStatus_(false), - dimseErrorStatus_(0x0000) + details_(details) { InitInternal(job); }
--- a/OrthancFramework/Sources/JobsEngine/JobStatus.h Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/JobsEngine/JobStatus.h Fri Nov 14 15:12:16 2025 +0100 @@ -26,6 +26,7 @@ #include "IJob.h" #include "../OrthancException.h" +#include <boost/shared_ptr.hpp> namespace Orthanc { @@ -39,8 +40,7 @@ Json::Value serialized_; bool hasSerialized_; std::string details_; - bool hasDimseErrorStatus_; - uint16_t dimseErrorStatus_; + Json::Value errorPayload_; Json::Value userData_; void InitInternal(const IJob& job); @@ -51,7 +51,7 @@ JobStatus(ErrorCode code, const std::string& details, const IJob& job, - uint16_t dimseErrorStatus); + const Json::Value& errorPayload); JobStatus(ErrorCode code, const std::string& details, @@ -109,19 +109,19 @@ return userData_; } - bool HasDimseErrorStatus() const + bool HasErrorPayload() const { - return hasDimseErrorStatus_; + return !errorPayload_.isNull(); } - uint16_t GetDimseErrorStatus() const + const Json::Value& GetErrorPayload() const { - if (!hasDimseErrorStatus_) + if (!HasErrorPayload()) { throw OrthancException(ErrorCode_BadSequenceOfCalls); } - return dimseErrorStatus_; + return errorPayload_; } }; }
--- a/OrthancFramework/Sources/JobsEngine/JobStepResult.cpp Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/JobsEngine/JobStepResult.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -32,9 +32,7 @@ JobStepResult::JobStepResult() : code_(JobStepCode_Failure), timeout_(0), - error_(ErrorCode_InternalError), - hasDimseErrorStatus_(false), - dimseErrorStatus_(0x0000) + error_(ErrorCode_InternalError) { } @@ -72,12 +70,10 @@ JobStepResult JobStepResult::Failure(const ErrorCode& error, const char* details, - uint16_t dimseErrorStatus) + const Json::Value& errorPayload) { JobStepResult result = Failure(error, details); - - result.hasDimseErrorStatus_ = true; - result.dimseErrorStatus_ = dimseErrorStatus; + result.errorPayload_ = errorPayload; return result; } @@ -85,11 +81,11 @@ JobStepResult JobStepResult::Failure(const OrthancException& exception) { - if (exception.HasDimseErrorStatus()) + if (exception.HasPayload()) { return Failure(exception.GetErrorCode(), exception.HasDetails() ? exception.GetDetails() : NULL, - exception.GetDimseErrorStatus()); + exception.GetPayload()); } else { @@ -142,19 +138,19 @@ } } - bool JobStepResult::HasDimseErrorStatus() const + bool JobStepResult::HasErrorPayload() const { - return hasDimseErrorStatus_; + return !errorPayload_.isNull(); } - uint16_t JobStepResult::GetDimseErrorStatus() const + const Json::Value& JobStepResult::GetErrorPayload() const { - if (!hasDimseErrorStatus_) + if (!HasErrorPayload()) { throw OrthancException(ErrorCode_BadSequenceOfCalls); } - return dimseErrorStatus_; + return errorPayload_; } }
--- a/OrthancFramework/Sources/JobsEngine/JobStepResult.h Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/JobsEngine/JobStepResult.h Fri Nov 14 15:12:16 2025 +0100 @@ -25,13 +25,14 @@ #pragma once #include "../Enumerations.h" - +#include <boost/shared_ptr.hpp> #include <json/value.h> #include <stdint.h> namespace Orthanc { class OrthancException; + class IExceptionPayload; class ORTHANC_PUBLIC JobStepResult { @@ -40,16 +41,12 @@ unsigned int timeout_; ErrorCode error_; std::string failureDetails_; - bool hasDimseErrorStatus_; - uint16_t dimseErrorStatus_; - + Json::Value errorPayload_; explicit JobStepResult(JobStepCode code) : code_(code), timeout_(0), - error_(ErrorCode_Success), - hasDimseErrorStatus_(false), - dimseErrorStatus_(0x0000) + error_(ErrorCode_Success) { } @@ -67,7 +64,7 @@ static JobStepResult Failure(const ErrorCode& error, const char* details, - uint16_t dimseErrorStatus); + const Json::Value& errorPayload); static JobStepResult Failure(const OrthancException& exception); @@ -79,8 +76,8 @@ const std::string& GetFailureDetails() const; - bool HasDimseErrorStatus() const; + bool HasErrorPayload() const; - uint16_t GetDimseErrorStatus() const; + const Json::Value& GetErrorPayload() const; }; }
--- a/OrthancFramework/Sources/JobsEngine/JobsEngine.cpp Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/JobsEngine/JobsEngine.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -87,9 +87,9 @@ case JobStepCode_Failure: running.GetJob().Stop(JobStopReason_Failure); - if (result.HasDimseErrorStatus()) + if (result.HasErrorPayload()) { - running.UpdateStatus(result.GetFailureCode(), result.GetFailureDetails(), result.GetDimseErrorStatus()); + running.UpdateStatus(result.GetFailureCode(), result.GetFailureDetails(), result.GetErrorPayload()); } else {
--- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -44,7 +44,7 @@ static const char* ERROR_CODE = "ErrorCode"; static const char* ERROR_DETAILS = "ErrorDetails"; static const char* USER_DATA = "UserData"; - static const char* DIMSE_ERROR_STATUS = "DimseErrorStatus"; + static const char* ERROR_PAYLOAD = "ErrorPayload"; class JobsRegistry::JobHandler : public boost::noncopyable @@ -305,9 +305,9 @@ target[USER_DATA] = userData; } - if (lastStatus_.HasDimseErrorStatus()) + if (lastStatus_.HasErrorPayload()) { - target[DIMSE_ERROR_STATUS] = lastStatus_.GetDimseErrorStatus(); + target[ERROR_PAYLOAD] = lastStatus_.GetErrorPayload(); } return true; @@ -361,9 +361,9 @@ job_->SetUserData(serialized[USER_DATA]); } - if (serialized.isMember(DIMSE_ERROR_STATUS)) + if (serialized.isMember(ERROR_PAYLOAD)) { - lastStatus_ = JobStatus(errorCode, details, *job_, static_cast<uint16_t>(serialized[DIMSE_ERROR_STATUS].asUInt())); + lastStatus_ = JobStatus(errorCode, details, *job_, serialized[ERROR_PAYLOAD]); } else { @@ -896,9 +896,15 @@ ErrorCode code = it->second->GetLastStatus().GetErrorCode(); const std::string& details = it->second->GetLastStatus().GetDetails(); - if (it->second->GetLastStatus().HasDimseErrorStatus()) + // Prefer the error payload from the job level if there is one since it should contain more information than the step error payload. + Json::Value jobErrorPayload; + if (it->second->GetJob().LookupErrorPayload(jobErrorPayload)) { - throw OrthancException(code, details, it->second->GetLastStatus().GetDimseErrorStatus()); + throw OrthancException(code, details, jobErrorPayload); + } + else if (it->second->GetLastStatus().HasErrorPayload()) + { + throw OrthancException(code, details, it->second->GetLastStatus().GetErrorPayload()); } else if (!details.empty()) { @@ -1465,7 +1471,7 @@ void JobsRegistry::RunningJob::UpdateStatus(ErrorCode code, const std::string& details, - uint16_t dimseErrorStatus) + const Json::Value& errorPayload) { if (!IsValid()) { @@ -1473,7 +1479,7 @@ } else { - JobStatus status(code, details, *job_, dimseErrorStatus); + JobStatus status(code, details, *job_, errorPayload); boost::mutex::scoped_lock lock(registry_.mutex_); registry_.CheckInvariants();
--- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.h Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.h Fri Nov 14 15:12:16 2025 +0100 @@ -246,7 +246,7 @@ void UpdateStatus(ErrorCode code, const std::string& details, - uint16_t dimseErrorStatus); + const Json::Value& errorPayload); }; }; }
--- a/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h Fri Nov 14 15:12:16 2025 +0100 @@ -150,5 +150,10 @@ { return false; } + + virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE + { + return false; + } }; }
--- a/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h Fri Nov 14 15:12:16 2025 +0100 @@ -133,5 +133,10 @@ return false; } + virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE + { + return false; + } + }; }
--- a/OrthancFramework/Sources/OrthancException.cpp Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/OrthancException.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -33,86 +33,87 @@ OrthancException::OrthancException(const OrthancException& other) : errorCode_(other.errorCode_), httpStatus_(other.httpStatus_), - logged_(false), - hasDimseErrorStatus_(other.hasDimseErrorStatus_), - dimseErrorStatus_(other.dimseErrorStatus_) + logged_(false) { if (other.details_.get() != NULL) { details_.reset(new std::string(*other.details_)); } + + // TODO: shouldn't we avoid copies of OrthancException completely ? + if (other.HasPayload()) + { + payload_.reset(new Json::Value(other.GetPayload())); + } } OrthancException::OrthancException(ErrorCode errorCode) : errorCode_(errorCode), httpStatus_(ConvertErrorCodeToHttpStatus(errorCode)), - logged_(false), - hasDimseErrorStatus_(false), - dimseErrorStatus_(0x0000) + logged_(false) { } OrthancException::OrthancException(ErrorCode errorCode, const std::string& details, - bool log) : + LogException log) : errorCode_(errorCode), httpStatus_(ConvertErrorCodeToHttpStatus(errorCode)), - logged_(log), - details_(new std::string(details)), - hasDimseErrorStatus_(false), - dimseErrorStatus_(0x0000) + logged_(log == LogException_Yes), + details_(new std::string(details)) { #if ORTHANC_ENABLE_LOGGING == 1 - if (log) + if (log == LogException_Yes) { LOG(ERROR) << EnumerationToString(errorCode_) << ": " << details; } #endif } + + OrthancException::OrthancException(ErrorCode errorCode, + HttpStatus httpStatus) : + errorCode_(errorCode), + httpStatus_(httpStatus), + logged_(false) + { + } + + OrthancException::OrthancException(ErrorCode errorCode, const std::string& details, - uint16_t dimseErrorStatus, - bool log) : + const Json::Value& payload, + LogException log) : errorCode_(errorCode), httpStatus_(ConvertErrorCodeToHttpStatus(errorCode)), - logged_(log), - details_(new std::string(details)), - hasDimseErrorStatus_(true), - dimseErrorStatus_(dimseErrorStatus) + logged_(log == LogException_Yes), + details_(new std::string(details)) { + payload_.reset(new Json::Value(payload)); + #if ORTHANC_ENABLE_LOGGING == 1 - if (log) + if (log == LogException_Yes) { LOG(ERROR) << EnumerationToString(errorCode_) << ": " << details; } #endif } - OrthancException::OrthancException(ErrorCode errorCode, - HttpStatus httpStatus) : - errorCode_(errorCode), - httpStatus_(httpStatus), - logged_(false), - hasDimseErrorStatus_(false), - dimseErrorStatus_(0x0000) - { - } OrthancException::OrthancException(ErrorCode errorCode, HttpStatus httpStatus, const std::string& details, - uint16_t dimseErrorStatus, - bool log) : + const Json::Value& payload, + LogException log) : errorCode_(errorCode), httpStatus_(httpStatus), - logged_(log), - details_(new std::string(details)), - hasDimseErrorStatus_(true), - dimseErrorStatus_(dimseErrorStatus) + logged_(log == LogException_Yes), + details_(new std::string(details)) { + payload_.reset(new Json::Value(payload)); + #if ORTHANC_ENABLE_LOGGING == 1 - if (log) + if (log == LogException_Yes) { LOG(ERROR) << EnumerationToString(errorCode_) << ": " << details; } @@ -122,16 +123,14 @@ OrthancException::OrthancException(ErrorCode errorCode, HttpStatus httpStatus, const std::string& details, - bool log) : + LogException log) : errorCode_(errorCode), httpStatus_(httpStatus), - logged_(log), - details_(new std::string(details)), - hasDimseErrorStatus_(false), - dimseErrorStatus_(0x0000) + logged_(log == LogException_Yes), + details_(new std::string(details)) { #if ORTHANC_ENABLE_LOGGING == 1 - if (log) + if (log == LogException_Yes) { LOG(ERROR) << EnumerationToString(errorCode_) << ": " << details; } @@ -170,19 +169,19 @@ } } - bool OrthancException::HasDimseErrorStatus() const + bool OrthancException::HasPayload() const { - return hasDimseErrorStatus_; + return payload_.get() != NULL; } - uint16_t OrthancException::GetDimseErrorStatus() const + const Json::Value& OrthancException::GetPayload() const { - if (!hasDimseErrorStatus_) + if (payload_.get() == NULL) { throw OrthancException(ErrorCode_BadSequenceOfCalls); } - return dimseErrorStatus_; + return *payload_; } bool OrthancException::HasBeenLogged() const
--- a/OrthancFramework/Sources/OrthancException.h Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/OrthancException.h Fri Nov 14 15:12:16 2025 +0100 @@ -27,11 +27,24 @@ #include "Compatibility.h" // For std::unique_ptr<> #include "Enumerations.h" #include "OrthancFramework.h" - +#include <json/value.h> #include <stdint.h> // For uint16_t namespace Orthanc { + static const char* const ERROR_PAYLOAD_TYPE = "Type"; + + // From 1.12.10, we use this enumeration instead of a bool to avoid implicit + // conversions to bool in OrthancException constructors because any type is + // automaticaly casted to a bool without a single warning which led to wrong + // constructor flavors being called. + enum ORTHANC_PUBLIC LogException + { + LogException_Yes, + LogException_No + }; + + class ORTHANC_PUBLIC OrthancException { private: @@ -47,8 +60,7 @@ std::unique_ptr<std::string> details_; // New in Orthanc 1.12.10 - bool hasDimseErrorStatus_; - uint16_t dimseErrorStatus_; + std::unique_ptr<Json::Value> payload_; public: OrthancException(const OrthancException& other); @@ -57,12 +69,12 @@ OrthancException(ErrorCode errorCode, const std::string& details, - bool log = true); + LogException log = LogException_Yes); OrthancException(ErrorCode errorCode, const std::string& details, - uint16_t dimseErrorStatus, - bool log = true); + const Json::Value& payload, + LogException log = LogException_Yes); OrthancException(ErrorCode errorCode, HttpStatus httpStatus); @@ -70,13 +82,14 @@ OrthancException(ErrorCode errorCode, HttpStatus httpStatus, const std::string& details, - bool log = true); + LogException log = LogException_Yes); + OrthancException(ErrorCode errorCode, HttpStatus httpStatus, const std::string& details, - uint16_t dimseErrorStatus, - bool log = true); + const Json::Value& payload, + LogException log = LogException_Yes); ErrorCode GetErrorCode() const; @@ -90,8 +103,8 @@ bool HasBeenLogged() const; - bool HasDimseErrorStatus() const; - - uint16_t GetDimseErrorStatus() const; + bool HasPayload() const; + + const Json::Value& GetPayload() const; }; }
--- a/OrthancFramework/Sources/SystemToolbox.cpp Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/Sources/SystemToolbox.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -229,7 +229,7 @@ { throw OrthancException(ErrorCode_RegularFileExpected, "The path does not point to a regular file: " + PathToUtf8(path), - log); + (log ? LogException_Yes : LogException_No)); } try @@ -239,7 +239,7 @@ if (!f.good()) { throw OrthancException(ErrorCode_InexistentFile, "File not found: " + PathToUtf8(path), - log); + (log ? LogException_Yes : LogException_No)); } std::streamsize size = GetStreamSize(f);
--- a/OrthancFramework/UnitTestsSources/JobsTests.cpp Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancFramework/UnitTestsSources/JobsTests.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -147,6 +147,11 @@ { return false; } + + virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE + { + return false; + } };
--- a/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancServer/Plugins/Engine/OrthancPlugins.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -1455,7 +1455,7 @@ { throw OrthancException(static_cast<ErrorCode>(error), GetErrorDetails(), - IsLogDetails()); + (IsLogDetails() ? LogException_Yes : LogException_No)); } else {
--- a/OrthancServer/Plugins/Engine/PluginsJob.h Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancServer/Plugins/Engine/PluginsJob.h Fri Nov 14 15:12:16 2025 +0100 @@ -95,6 +95,11 @@ return false; } + virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE + { + return false; + } + }; }
--- a/OrthancServer/Sources/ServerJobs/ArchiveJob.h Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/ArchiveJob.h Fri Nov 14 15:12:16 2025 +0100 @@ -153,5 +153,10 @@ } return false; } + + virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE + { + return false; + } }; }
--- a/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -24,6 +24,7 @@ #include "DicomGetScuJob.h" #include "../../../OrthancFramework/Sources/DicomParsing/FromDcmtkBridge.h" +#include "../../../OrthancFramework/Sources/DicomNetworking/DimseErrorPayload.h" #include "../../../OrthancFramework/Sources/SerializationToolbox.h" #include "../ServerContext.h" #include <dcmtk/dcmnet/dimse.h> @@ -100,8 +101,12 @@ } catch (OrthancException& e) { - dimseErrorStatus_ = e.GetDimseErrorStatus(); - throw e; + if (e.HasPayload() && IsDimseErrorStatusPayload(e.GetPayload())) + { + dimseErrorStatus_ = GetDimseErrorStatusFromPayload(e.GetPayload()); + } + + throw; } return true; } @@ -368,4 +373,14 @@ currentCommand_->AddReceivedInstance(instanceId); } } + + bool DicomRetrieveScuBaseJob::LookupErrorPayload(Json::Value& payload) const + { + Json::Value publicContent; + + GetPublicContent(publicContent); + payload = publicContent["Details"]; + + return true; + } }
--- a/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/DicomRetrieveScuBaseJob.h Fri Nov 14 15:12:16 2025 +0100 @@ -173,5 +173,8 @@ static void AddReceivedInstanceFromCStore(uint16_t originatorMessageId, const std::string& originatorAet, const std::string& instanceId); + + + virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE; }; }
--- a/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.h Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancServer/Sources/ServerJobs/ThreadedSetOfInstancesJob.h Fri Nov 14 15:12:16 2025 +0100 @@ -174,5 +174,10 @@ virtual void SetUserData(const Json::Value& userData) ORTHANC_OVERRIDE; virtual bool GetUserData(Json::Value& userData) const ORTHANC_OVERRIDE; + + virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE + { + return false; + } }; }
--- a/OrthancServer/Sources/main.cpp Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancServer/Sources/main.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -714,9 +714,9 @@ message["Details"] = exception.GetDetails(); } - if (exception.HasDimseErrorStatus()) + if (exception.HasPayload()) { - message["DimseErrorStatus"] = exception.GetDimseErrorStatus(); + message["ErrorPayload"] = exception.GetPayload(); } std::string info = message.toStyledString();
--- a/OrthancServer/UnitTestsSources/ServerJobsTests.cpp Thu Nov 13 06:32:48 2025 +0100 +++ b/OrthancServer/UnitTestsSources/ServerJobsTests.cpp Fri Nov 14 15:12:16 2025 +0100 @@ -151,6 +151,11 @@ { return false; } + + virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE + { + return false; + } }; @@ -226,6 +231,11 @@ { return false; } + + virtual bool LookupErrorPayload(Json::Value& payload) const ORTHANC_OVERRIDE + { + return false; + } };
