Files
speckle-cpp-connectors/SpeckleLib/Speckle/Interface/Browser/Bridge/Functions/RunMethod.cpp
T
Ralph Wessel 696a4ea870 Added TestBinding and implemented associated test methods
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
2024-08-27 16:48:42 +01:00

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