696a4ea870
Added speckle::utility::Exception Added ErrorReport for BrowserBridge method execution JSBridgeArgument is capable of capturing error conditions for later reporting Implemented additional exception handling and return report to JS on method execution failure
139 lines
5.1 KiB
C++
139 lines
5.1 KiB
C++
#include "Speckle/Interface/Browser/Bridge/Functions/RunMethod.h"
|
|
|
|
#include "Active/Serialise/CargoHold.h"
|
|
#include "Active/Serialise/Package/PackageWrap.h"
|
|
#include "Speckle/Interface/Browser/Bridge/BrowserBridge.h"
|
|
#include "Speckle/Interface/Browser/Bridge/Functions/ErrorReport.h"
|
|
#include "Speckle/Utility/Exception.h"
|
|
|
|
#ifdef ARCHICAD
|
|
#include <ACAPinc.h>
|
|
#include <MessageLoopExecutor.hpp>
|
|
#endif
|
|
|
|
using namespace active::serialise;
|
|
using namespace speckle::interfac::browser;
|
|
using namespace speckle::interfac::browser::bridge;
|
|
using namespace speckle::serialise::jsbase;
|
|
using namespace speckle::utility;
|
|
|
|
namespace {
|
|
|
|
///Default error message used when no description is available from the exception
|
|
const char* defaultError = "An unidentified exception was thrown";
|
|
|
|
/*!
|
|
Format the error message from an exception thrown during bridge method execution
|
|
@param message The exception error message (what happened)
|
|
@param argument The method argument (also carries the method name etc)
|
|
@return The formatted message
|
|
*/
|
|
String formattedErrorMessage(const String& message, const JSBridgeArgument& argument) {
|
|
return "Exception thrown by the Speckle connector executing method '" + argument.getMethodName() + "': \"" + message + "\"";
|
|
} //formattedErrorMessage
|
|
|
|
|
|
/*!
|
|
Execute a bridge method using a specified argument
|
|
@param bridge The bridge
|
|
@param method The bridge method to execute
|
|
@param argument The method argument
|
|
*/
|
|
void executeMethod(BrowserBridge& bridge, Functional<>& method, JSBridgeArgument& argument) {
|
|
std::optional<ErrorReport> errorReport;
|
|
//If the argument validation failed (during deserialisation) then we simply fill in the exception report without running the method
|
|
if (argument.hasError())
|
|
errorReport = ErrorReport{argument.errorMessage().value_or(String{})};
|
|
else {
|
|
try {
|
|
//Execute the method with the supplied argument
|
|
auto result = method.execute(argument);
|
|
//Cache the result in the bridge as required (when we have a request ID and a non-void result)
|
|
if (result && !argument.getRequestID().empty())
|
|
bridge.cacheResult(std::move(result), argument.getRequestID());
|
|
return;
|
|
} catch(std::runtime_error e) {
|
|
//NB: This will capture the response from both Speckle and low-level system/runtime error exceptions
|
|
errorReport = ErrorReport{e.what(), typeid(e).name()};
|
|
} catch(...) {}
|
|
}
|
|
//This point is only reached in error conditions - ensure a response is provided if it hasn't been establishd already
|
|
if (!errorReport)
|
|
errorReport = ErrorReport{defaultError};
|
|
errorReport->message = formattedErrorMessage(errorReport->message, argument);
|
|
//Cache the error report to be sent back to the JS caller against the request ID
|
|
bridge.cacheResult(std::make_unique<CargoHold<PackageWrap, ErrorReport>>(*errorReport), argument.getRequestID());
|
|
} //executeMethod
|
|
|
|
|
|
///NB: The following is an Archicad-specific method for executing a function asynchronously within the app event queue
|
|
///Implement for other platforms as required
|
|
#ifdef ARCHICAD
|
|
/*!
|
|
Scheduled task to execute a browser bridge method in the Archicad event queue
|
|
*/
|
|
class RunBrowserMethod : public GS::Runnable {
|
|
public:
|
|
/*!
|
|
Constructor
|
|
@param bridge The parent browser bridge
|
|
@param method The bridge method to execute
|
|
@param argument The method argument
|
|
*/
|
|
RunBrowserMethod(BrowserBridge& bridge, Functional<>& method, std::shared_ptr<JSBridgeArgument> argument) :
|
|
m_bridge{bridge}, m_method{method}, m_argument{argument} {}
|
|
|
|
/*!
|
|
Execute the function and (when required) cache the result or an error report
|
|
*/
|
|
void Run() override {
|
|
if (m_argument)
|
|
executeMethod(m_bridge, m_method, *m_argument);
|
|
}
|
|
|
|
private:
|
|
///The parent browser bridge
|
|
BrowserBridge& m_bridge;
|
|
///The bridge method to execute
|
|
Functional<>& m_method;
|
|
///The method argument
|
|
std::shared_ptr<JSBridgeArgument> m_argument;
|
|
};
|
|
#endif
|
|
|
|
}
|
|
|
|
/*--------------------------------------------------------------------
|
|
Constructor
|
|
|
|
bridge: The parent bridge object (provides access to bridge methods)
|
|
--------------------------------------------------------------------*/
|
|
RunMethod::RunMethod(BrowserBridge& bridge) : m_bridge{bridge},
|
|
JSFunction{"RunMethod", [&](auto args) {
|
|
runMethod(args);
|
|
}} {
|
|
} //RunMethod::RunMethod
|
|
|
|
|
|
/*--------------------------------------------------------------------
|
|
Run a specified bridge method
|
|
|
|
arguments: The method arguments
|
|
--------------------------------------------------------------------*/
|
|
void RunMethod::runMethod(JSBridgeArgumentWrap& argument) const {
|
|
//Confirm argument and function validity
|
|
if (!argument)
|
|
return;
|
|
auto method = m_bridge.getMethod(argument.getMethodName());
|
|
if (method == nullptr)
|
|
return;
|
|
//NB: Implement the equivalent run scheduling for other platforms as required
|
|
#ifdef ARCHICAD
|
|
GS::MessageLoopExecutor executor;
|
|
try {
|
|
executor.Execute(new RunBrowserMethod(m_bridge, *method, argument.get()));
|
|
}
|
|
catch (...) {}
|
|
#endif
|
|
} //RunMethod::runMethod
|