Skip to content

Commit

Permalink
33: Add Error Handling Support (#61)
Browse files Browse the repository at this point in the history
Add error handling APIs to libmexclass.

Co-authored-by: Kevin Gurney <kgurney@mathworks.com>
  • Loading branch information
sgilmore10 and kevingurney committed Jun 1, 2023
1 parent 3eaa37c commit 77f3d72
Show file tree
Hide file tree
Showing 22 changed files with 162 additions and 39 deletions.
6 changes: 3 additions & 3 deletions example/CarProxyFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

#include "proxy/Car.h"

std::shared_ptr<libmexclass::proxy::Proxy>
CarProxyFactory::make_proxy(const libmexclass::proxy::ClassName& class_name,
libmexclass::proxy::MakeResult CarProxyFactory::make_proxy(const libmexclass::proxy::ClassName& class_name,
const libmexclass::proxy::FunctionArguments& constructor_arguments) {

REGISTER_PROXY(example.proxy.Car, example::proxy::Car);
return nullptr;

return libmexclass::error::Error{"libmexclass:proxy:factory:failed", "Failed to make proxy"};
}
3 changes: 1 addition & 2 deletions example/CarProxyFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
class CarProxyFactory : public libmexclass::proxy::Factory {
public:
CarProxyFactory() {}
virtual std::shared_ptr<libmexclass::proxy::Proxy>
make_proxy(const libmexclass::proxy::ClassName& class_name,
virtual libmexclass::proxy::MakeResult make_proxy(const libmexclass::proxy::ClassName& class_name,
const libmexclass::proxy::FunctionArguments& constructor_arguments);
};
29 changes: 26 additions & 3 deletions example/proxy/Car.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,17 @@ void Car::GetSpeed(libmexclass::proxy::method::Context& context) {

void Car::SetSpeed(libmexclass::proxy::method::Context& context) {
// First, cast the first cell array element into a MDA TypedArray<uint64_t> array.
matlab::data::TypedArray<uint64_t> state_mda = context.inputs[0];
matlab::data::TypedArray<uint64_t> speed_mda = context.inputs[0];
// Second, extract the first [0th] element from the MDA TypedArray and convert it into a uint64_t value.
const std::uint64_t state = uint64_t(state_mda[0]);
const std::uint64_t speed = uint64_t(speed_mda[0]);
if (speed > 100) {
context.error = libmexclass::error::Error{"Car:Speed:TooFast", "Slow down!!"};
return;
}


// Third, pass the unwrapped native C++ int64_t value to the raw C++ object Headlights() method.
car.SetSpeed(state);
car.SetSpeed(speed);
}

void Car::GetMake(libmexclass::proxy::method::Context& context) {
Expand Down Expand Up @@ -79,4 +85,21 @@ void Car::GetColor(libmexclass::proxy::method::Context& context) {
void Car::Print(libmexclass::proxy::method::Context& context) {
car.Print();
}

std::string convert(const libmexclass::proxy::FunctionArguments& constructor_arguments, std::uint64_t index) {
matlab::data::StringArray mda = constructor_arguments[index];
return std::string(mda[0]);
}

libmexclass::proxy::MakeResult Car::make(const libmexclass::proxy::FunctionArguments& constructor_arguments) {
const std::string make = convert(constructor_arguments, 0);
const std::string model = convert(constructor_arguments, 1);
const std::string color = convert(constructor_arguments, 2);

if (make.empty() || model.empty() || color.empty()) {
return libmexclass::error::Error{"libmexclass:example:Car:EmptyInputs", "Inputs make, model, and color must be nonempty"};
}
return std::make_shared<example::proxy::Car>(make, model, color);
}

} // namespace example::proxy
13 changes: 6 additions & 7 deletions example/proxy/Car.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
#include "../Car.h"

namespace example::proxy {

class Car : public libmexclass::proxy::Proxy {
public:
Car(const libmexclass::proxy::FunctionArguments& constructor_arguments)
: car{convert(constructor_arguments, 0), convert(constructor_arguments, 1), convert(constructor_arguments, 2)} {
Car(const std::string& make, const std::string& model, const std::string& color)
: car{make, model, color} {

// Step 1. Unpack constructor arguments.

// Step 2. Initiliaze "raw" C++ object.
Expand All @@ -27,6 +29,8 @@ class Car : public libmexclass::proxy::Proxy {
REGISTER_METHOD(Car, GetColor);
REGISTER_METHOD(Car, Print);
}

static libmexclass::proxy::MakeResult make(const libmexclass::proxy::FunctionArguments& constructor_arguments);

private:
void Accelerate(libmexclass::proxy::method::Context& context);
Expand All @@ -40,11 +44,6 @@ class Car : public libmexclass::proxy::Proxy {
void GetColor(libmexclass::proxy::method::Context& context);
void Print(libmexclass::proxy::method::Context& context);

std::string convert(const libmexclass::proxy::FunctionArguments& constructor_arguments, std::uint64_t index) const {
matlab::data::StringArray mda = constructor_arguments[index];
return std::string(mda[0]);
}

// Note: Clients can replace this with whatever "raw" C++ Class they want
// to proxy method calls to.
example::Car car;
Expand Down
1 change: 1 addition & 0 deletions libmexclass/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ set(LIBMEXCLASS_SOURCES
${LIBMEXCLASS_SOURCE_DIR}/libmexclass/action/MethodCall.cpp
${LIBMEXCLASS_SOURCE_DIR}/libmexclass/proxy/Proxy.cpp
${LIBMEXCLASS_SOURCE_DIR}/libmexclass/proxy/ProxyManager.cpp
${LIBMEXCLASS_SOURCE_DIR}/libmexclass/error/Error.cpp
)

# Create libmexclass shared library target.
Expand Down
6 changes: 5 additions & 1 deletion libmexclass/cpp/source/include/libmexclass/action/Action.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#pragma once

#include "libmexclass/mex/State.h"
#include "libmexclass/error/Error.h"

#include <optional>

namespace libmexclass::action {

Expand All @@ -13,7 +16,8 @@ namespace libmexclass::action {
public:
Action(libmexclass::mex::State& state) : outputs{state.outputs}, matlab{state.matlab} { }
virtual ~Action() {}
virtual void execute() = 0;
virtual std::optional<libmexclass::error::Error> execute() = 0;

protected:
libmexclass::mex::Arguments& outputs;
libmexclass::mex::Matlab matlab;
Expand Down
2 changes: 1 addition & 1 deletion libmexclass/cpp/source/include/libmexclass/action/Create.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ namespace libmexclass::action {
constructor_arguments = state.inputs[libmexclass::action::Create::CONSTRUCTOR_ARGUMENTS_INDEX];
}
virtual ~Create() {}
virtual void execute() override;
virtual std::optional<libmexclass::error::Error> execute() override;

private:
std::shared_ptr<proxy::Factory> proxy_factory;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace libmexclass::action {
proxy_id = std::uint64_t(state.inputs[libmexclass::action::Destroy::PROXY_ID_INDEX][0]);
}
virtual ~Destroy() {}
virtual void execute() override;
virtual std::optional<libmexclass::error::Error> execute() override;
private:
libmexclass::proxy::ID proxy_id;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace libmexclass::action {
method_arguments = state.inputs[libmexclass::action::MethodCall::METHOD_ARGUMENTS_INDEX];
}
virtual ~MethodCall() {}
virtual void execute() override;
virtual std::optional<libmexclass::error::Error> execute() override;
private:
libmexclass::proxy::ID proxy_id;
libmexclass::proxy::method::Name method_name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace libmexclass::action {
Unsupported(libmexclass::mex::State& state) : Action{state} {
}
virtual ~Unsupported() {}
virtual void execute() override;
virtual std::optional<libmexclass::error::Error> execute() override;
};

}
38 changes: 38 additions & 0 deletions libmexclass/cpp/source/include/libmexclass/error/Error.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#pragma once

#include <string>

namespace libmexclass::error {

class Error {
public:
Error(const std::string& error_id, const std::string& error_message)
: id_{error_id}
, message_{error_message} {}

const std::string& id() {
return id_;
}

const std::string& message() {
return message_;
}

private:
std::string id_;
std::string message_;
};

class ErrorBuilder {
public:
ErrorBuilder& id(const std::string& error_id);

ErrorBuilder& message(const std::string& error_message);

Error build() const;

private:
std::string id_;
std::string message_;
};
}
16 changes: 15 additions & 1 deletion libmexclass/cpp/source/include/libmexclass/mex/gateway.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "libmexclass/mex/Matlab.h"
#include "libmexclass/mex/State.h"
#include "libmexclass/proxy/Factory.h"
#include "libmexclass/error/Error.h"

#include "mex.hpp"
#include "mexAdapter.hpp"
Expand Down Expand Up @@ -67,6 +68,19 @@ namespace libmexclass::mex {
std::unique_ptr<action::Action> action = action_factory.makeAction(state);

// Execute the Action.
action->execute();
std::optional<error::Error> maybe_error = action->execute();
if (maybe_error) {
matlab::data::ArrayFactory factory;

auto error = maybe_error.value();
auto id = matlab::engine::convertUTF8StringToUTF16String(error.id());
auto message = matlab::engine::convertUTF8StringToUTF16String(error.message());

matlab::data::StructArray errorStruct = factory.createStructArray({1, 1}, {"identifier", "message"});
errorStruct[0]["identifier"] = factory.createScalar(id);
errorStruct[0]["message"] = factory.createScalar(message);
matlab->feval(u"error", 0, std::vector<matlab::data::Array>({errorStruct}));

}
}
}
8 changes: 6 additions & 2 deletions libmexclass/cpp/source/include/libmexclass/proxy/Factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,20 @@
* \brief register a C++ Proxy subclass so that a MATLAB Proxy instance can connect with it
* \param name the name that a MATLAB Proxy instance will use to connect with the registered C++ Proxy subclass
* \param cppClass the name of the C++ Proxy subclass to register
* \note The C++ Proxy class must define a static make function with the following signature:
* libmexclass::error::Result<std::shared_ptr<cppClass>> make(libmexclass::proxy::FunctionArguments&).
*/
#define REGISTER_PROXY(name, cppClass) if (class_name.compare(#name) == 0) return std::make_shared<cppClass>(constructor_arguments)

#define REGISTER_PROXY(name, cppClass) if (class_name.compare(#name) == 0) return cppClass::make(constructor_arguments)

namespace libmexclass::proxy {


// Abstract interface defining a class that is able to make Proxy objects with a particular ClassName and ConstructorArguments.
// This interface uses the "Factory" design pattern.
class Factory {
public:
virtual std::shared_ptr<Proxy> make_proxy(const libmexclass::proxy::ClassName& class_name, const libmexclass::proxy::FunctionArguments& constructor_arguments) = 0;
virtual libmexclass::proxy::MakeResult make_proxy(const libmexclass::proxy::ClassName& class_name, const libmexclass::proxy::FunctionArguments& constructor_arguments) = 0;
};

}
8 changes: 7 additions & 1 deletion libmexclass/cpp/source/include/libmexclass/proxy/Proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
#include "libmexclass/proxy/method/Name.h"
#include "libmexclass/proxy/method/Method.h"
#include "libmexclass/proxy/method/Object.h"
#include "libmexclass/error/Error.h"

#include <variant>
#include <optional>
#include <functional>
#include <unordered_map>

Expand All @@ -21,11 +24,14 @@
*/

namespace libmexclass::proxy {

class Proxy {
public:
Proxy() : methods{} { }
void invoke(libmexclass::proxy::method::Method& method);
std::optional<libmexclass::error::Error> invoke(libmexclass::proxy::method::Method& method);
protected:
std::unordered_map<libmexclass::proxy::method::Name, libmexclass::proxy::method::Object> methods;
};

using MakeResult = std::variant<std::shared_ptr<libmexclass::proxy::Proxy>, libmexclass::error::Error>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@
#include "libmexclass/proxy/method/Inputs.h"
#include "libmexclass/proxy/method/Outputs.h"
#include "libmexclass/proxy/method/Matlab.h"
#include "libmexclass/error/Error.h"

#include <optional>

namespace libmexclass::proxy::method {

struct Context {
const libmexclass::proxy::method::Inputs inputs;
libmexclass::proxy::method::Outputs outputs;
const libmexclass::proxy::method::Matlab matlab;
std::optional<libmexclass::error::Error> error;
};

}
14 changes: 11 additions & 3 deletions libmexclass/cpp/source/libmexclass/action/Create.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@

namespace libmexclass::action {

void Create::execute() {
std::optional<libmexclass::error::Error> Create::execute() {

// proxy::ProxyFactory will create an appropriate proxy::Proxy subclass
// based on the provided libmexclass::mex::State.
std::shared_ptr<libmexclass::proxy::Proxy> proxy =
proxy_factory->make_proxy(class_name, constructor_arguments);
libmexclass::proxy::MakeResult maybe_proxy = proxy_factory->make_proxy(class_name, constructor_arguments);

if (std::holds_alternative<libmexclass::error::Error>(maybe_proxy)) {
return std::get<libmexclass::error::Error>(maybe_proxy);
}

auto proxy = std::get<std::shared_ptr<libmexclass::proxy::Proxy>>(std::move(maybe_proxy));

// Assign the proxy::ID returned by the ProxyManager to the outputs[] MDA.
libmexclass::proxy::ID id =
Expand All @@ -21,6 +27,8 @@ void Create::execute() {
matlab::data::ArrayFactory factory;
auto id_array = factory.createScalar<libmexclass::proxy::ID>(id);
outputs[0] = id_array;

return std::nullopt;
}

} // namespace libmexclass::action
3 changes: 2 additions & 1 deletion libmexclass/cpp/source/libmexclass/action/Destroy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@

namespace libmexclass::action {

void Destroy::execute() {
std::optional<libmexclass::error::Error> Destroy::execute() {
// Remove the proxy::Proxy instance specified by the given proxy::ID from
// the proxy::ProxyManager. This should also deallocate any associated
// memory (unless there is a shared_ptr to the proxy being held somewhere
// else).
libmexclass::proxy::ProxyManager::unmanageProxy(proxy_id);
return std::nullopt;
}

} // namespace libmexclass::action
5 changes: 4 additions & 1 deletion libmexclass/cpp/source/libmexclass/action/MethodCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace libmexclass::action {

void MethodCall::execute() {
std::optional<libmexclass::error::Error> MethodCall::execute() {
// TODO: Implement ID retrieval logic from MethodCall properties.
// Retrieve the appropriate polymorphic proxy::Proxy instance from the
// proxy::ProxyManager using the given proxy::ID.
Expand All @@ -22,6 +22,9 @@ void MethodCall::execute() {
libmexclass::proxy::method::Method method{method_name, method_arguments,
outputs, matlab};
proxy->invoke(method);

// return an error if one exists. Otherwise returns a nullopt.
return method.context.error;
}

} // namespace libmexclass::action
7 changes: 2 additions & 5 deletions libmexclass/cpp/source/libmexclass/action/Unsupported.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@

namespace libmexclass::action {

void Unsupported::execute() {
// matlab::data::ArrayFactory factory;
// state.matlab->feval(u"error", 0,
// std::vector<matlab::data::Array>({ factory.createScalar("The specified action is unsupported.") }));
std::optional<libmexclass::error::Error> Unsupported::execute() {
return libmexclass::error::Error{"libmexclass:action:UnsupportedAction", "The specified action is unsupported"};
}

}
18 changes: 18 additions & 0 deletions libmexclass/cpp/source/libmexclass/error/Error.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include "libmexclass/error/Error.h"

namespace libmexclass::error {

ErrorBuilder& ErrorBuilder::id(const std::string& error_id) {
id_ = error_id;
return *this;
}

ErrorBuilder& ErrorBuilder::message(const std::string& error_message) {
message_ = error_message;
return *this;
}

Error ErrorBuilder::build() const {
return Error{id_, message_};
}
}

0 comments on commit 77f3d72

Please sign in to comment.