Skip to content

Commit 3d65c9c

Browse files
committed
feat(bmi-protocols)!: v0.2 of the protocols lib using expected semantics for error handling
1 parent 87fc646 commit 3d65c9c

File tree

5 files changed

+284
-120
lines changed

5 files changed

+284
-120
lines changed

include/utilities/bmi/mass_balance.hpp

Lines changed: 25 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,23 @@ See the License for the specific language governing permissions and
1515
limitations under the License.
1616
------------------------------------------------------------------------
1717
18+
Version 0.2
19+
Conform to updated protocol interface
20+
Removed integration and error exceptions in favor of ProtocolError
21+
1822
Version 0.1
1923
Interface of the BMI mass balance protocol
2024
*/
2125
#pragma once
2226

27+
2328
#include <string>
2429
#include <exception>
25-
#include "protocol.hpp"
30+
#include <protocol.hpp>
31+
#include <nonstd/expected.hpp>
2632

2733
namespace models{ namespace bmi{ namespace protocols{
28-
34+
using nonstd::expected;
2935
/** Mass balance variable names **/
3036
constexpr const char* const INPUT_MASS_NAME = "ngen::mass_in";
3137
constexpr const char* const OUTPUT_MASS_NAME = "ngen::mass_out";
@@ -40,7 +46,7 @@ namespace models{ namespace bmi{ namespace protocols{
4046
constexpr const char* const FATAL_KEY = "fatal";
4147
constexpr const char* const CHECK_KEY = "check";
4248
constexpr const char* const FREQUENCY_KEY = "frequency";
43-
49+
4450
class NgenMassBalance : public NgenBmiProtocol {
4551
/** @brief Mass Balance protocol
4652
*
@@ -58,9 +64,18 @@ namespace models{ namespace bmi{ namespace protocols{
5864
* @param model A shared pointer to a Bmi_Adapter object which should be
5965
* initialized before being passed to this constructor.
6066
*/
61-
NgenMassBalance(std::shared_ptr<models::bmi::Bmi_Adapter> model=nullptr) :
62-
NgenBmiProtocol(model), check(false), is_fatal(false),
63-
tolerance(1.0E-16), frequency(1){}
67+
NgenMassBalance(const ModelPtr& model, const Properties& properties);
68+
69+
/**
70+
* @brief Construct a new, default Ngen Mass Balance object
71+
*
72+
* By default, the protocol is considered unsupported and won't be checked
73+
*/
74+
NgenMassBalance();
75+
76+
virtual ~NgenMassBalance() override;
77+
78+
private:
6479

6580
/**
6681
* @brief Run the mass balance protocol
@@ -76,17 +91,14 @@ namespace models{ namespace bmi{ namespace protocols{
7691
* throws: MassBalanceError if the mass balance is not within the configured
7792
* acceptable tolerance and the protocol is configured to be fatal.
7893
*/
79-
void run(const Context& ctx) const override;
80-
81-
virtual ~NgenMassBalance() override {};
94+
auto run(const ModelPtr& model, const Context& ctx) const -> expected<void, ProtocolError> override;
8295

83-
private:
8496
/**
8597
* @brief Check if the mass balance protocol is supported by the model
8698
*
8799
* throws: MassBalanceIntegrationError if the mass balance protocol is not supported
88100
*/
89-
void check_support() override;
101+
[[nodiscard]] auto check_support(const ModelPtr& model) -> expected<void, ProtocolError> override;
90102

91103
/**
92104
* @brief Check the model for support and initialize the mass balance protocol from the given properties.
@@ -107,78 +119,15 @@ namespace models{ namespace bmi{ namespace protocols{
107119
* fatal: bool, default false. Whether to treat mass balance errors as fatal.
108120
* Otherwise, mass balance checking will be disabled (check will be false)
109121
*/
110-
void initialize(const geojson::PropertyMap& properties) override;
122+
auto initialize(const ModelPtr& model, const Properties& properties) -> expected<void, ProtocolError> override;
111123

124+
private:
112125
// Configurable options/values
113126
bool check;
114127
bool is_fatal;
115128
double tolerance;
116129
// How often (in time steps) to check mass balance
117130
int frequency;
118-
119-
/**
120-
* @brief Friend class for checking/managing support and initialization
121-
*
122-
* This allows the NgenBmiProtocols container class to access private members,
123-
* particularly the check_support() and initialize() methods.
124-
*
125-
*/
126-
friend class NgenBmiProtocols;
127-
128-
};
129-
130-
class MassBalanceIntegration : public std::exception {
131-
/**
132-
* @brief Exception thrown when there is an error in mass balance integration
133-
*
134-
* This indicates that the BMI model isn't capable of supporting the mass balance protocol.
135-
*/
136-
137-
public:
138-
139-
MassBalanceIntegration(char const *const message) noexcept : MassBalanceIntegration(std::string(message)) {}
140-
141-
MassBalanceIntegration(std::string message) noexcept : std::exception(), what_message(std::move(message)) {}
142-
143-
MassBalanceIntegration(MassBalanceIntegration &exception) noexcept : MassBalanceIntegration(exception.what_message) {}
144-
145-
MassBalanceIntegration(MassBalanceIntegration &&exception) noexcept
146-
: MassBalanceIntegration(std::move(exception.what_message)) {}
147-
148-
char const *what() const noexcept override {
149-
return what_message.c_str();
150-
}
151-
152-
private:
153-
154-
std::string what_message;
155-
};
156-
157-
class MassBalanceError : public std::exception {
158-
/**
159-
* @brief Exception thrown when a mass balance error occurs
160-
*
161-
* This indicates that a mass balance error has occurred within the model.
162-
*/
163-
164-
public:
165-
166-
MassBalanceError(char const *const message) noexcept : MassBalanceError(std::string(message)) {}
167-
168-
MassBalanceError(std::string message) noexcept : std::exception(), what_message(std::move(message)) {}
169-
170-
MassBalanceError(MassBalanceError &exception) noexcept : MassBalanceError(exception.what_message) {}
171-
172-
MassBalanceError(MassBalanceError &&exception) noexcept
173-
: MassBalanceError(std::move(exception.what_message)) {}
174-
175-
char const *what() const noexcept override {
176-
return what_message.c_str();
177-
}
178-
179-
private:
180-
181-
std::string what_message;
182131
};
183132

184133
}}}

include/utilities/bmi/protocol.hpp

Lines changed: 136 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ See the License for the specific language governing permissions and
1515
limitations under the License.
1616
------------------------------------------------------------------------
1717
18+
Version 0.2
19+
Enumerate protocol error types and add ProtocolError exception class
20+
Implement error handling via expected<T, ProtocolError> and error_or_warning
21+
Removed model member and required model reference in run(), check_support(), and initialize()
22+
Minor refactoring and style changes
23+
1824
Version 0.1
1925
Virtual interface for BMI protocols
2026
*/
@@ -23,9 +29,53 @@ Virtual interface for BMI protocols
2329

2430
#include "Bmi_Adapter.hpp"
2531
#include "JSONProperty.hpp"
26-
32+
#include <nonstd/expected.hpp>
2733

2834
namespace models{ namespace bmi{ namespace protocols{
35+
using nonstd::expected;
36+
using nonstd::make_unexpected;
37+
38+
enum class Error{
39+
UNITIALIZED_MODEL,
40+
UNSUPPORTED_PROTOCOL,
41+
INTEGRATION_ERROR,
42+
PROTOCOL_ERROR,
43+
PROTOCOL_WARNING
44+
};
45+
46+
class ProtocolError: public std::exception {
47+
public:
48+
ProtocolError () = delete;
49+
ProtocolError(Error err, const std::string& message="") : err(std::move(err)), message(std::move(message)) {}
50+
ProtocolError(const ProtocolError& other) = default;
51+
ProtocolError(ProtocolError&& other) noexcept = default;
52+
ProtocolError& operator=(const ProtocolError& other) = default;
53+
ProtocolError& operator=(ProtocolError&& other) noexcept = default;
54+
~ProtocolError() = default;
55+
56+
auto to_string() const -> std::string {
57+
switch (err) {
58+
case Error::UNITIALIZED_MODEL: return "Error(Uninitialized Model)::" + message;
59+
case Error::UNSUPPORTED_PROTOCOL: return "Warning(Unsupported Protocol)::" + message;
60+
case Error::INTEGRATION_ERROR: return "Error(Integration)::" + message;
61+
case Error::PROTOCOL_ERROR: return "Error(Protocol)::" + message;
62+
case Error::PROTOCOL_WARNING: return "Warning(Protocol)::" + message;
63+
default: return "Unknown Error: " + message;
64+
}
65+
}
66+
67+
auto error_code() const -> const Error& { return err; }
68+
auto get_message() const -> const std::string& { return message; }
69+
70+
char const *what() const noexcept override {
71+
message = to_string();
72+
return message.c_str();
73+
}
74+
75+
private:
76+
Error err;
77+
mutable std::string message;
78+
};
2979

3080
struct Context{
3181
const int current_time_step;
@@ -34,55 +84,125 @@ struct Context{
3484
const std::string& id;
3585
};
3686

87+
using ModelPtr = std::shared_ptr<models::bmi::Bmi_Adapter>;
88+
using Properties = geojson::PropertyMap;
89+
3790
class NgenBmiProtocol{
3891
/**
3992
* @brief Abstract interface for a generic BMI protocol
4093
*
4194
*/
4295

4396
public:
97+
4498
/**
45-
* @brief Run the BMI protocol
99+
* @brief Construct a new Ngen Bmi Protocol object
100+
*
101+
* By default, the protocol is considered unsupported.
102+
* Subclasses are responsible for implementing the check_support() method,
103+
* and ensuring that is_supported is properly set based on the protocol's
104+
* requirements.
46105
*
47106
*/
48-
virtual void run(const Context& ctx) const = 0;
107+
NgenBmiProtocol() : is_supported(false) {}
49108

50109
virtual ~NgenBmiProtocol() = default;
51110

52-
private:
111+
protected:
53112
/**
54-
* @brief Check if the BMI protocol is supported by the model
113+
* @brief Handle a ProtocolError by either throwing it or logging it as a warning
114+
*
115+
* @param err The ProtocolError to handle
116+
* @return expected<void, ProtocolError> Returns an empty expected if the error was logged as a warning,
117+
* otherwise throws the ProtocolError.
55118
*
119+
* @throws ProtocolError if the error is of type PROTOCOL_ERROR
56120
*/
57-
virtual void check_support() = 0;
121+
static auto error_or_warning(const ProtocolError& err) -> expected<void, ProtocolError> {
122+
// Log warnings, but throw errors
123+
switch(err.error_code()){
124+
case Error::PROTOCOL_ERROR:
125+
throw err;
126+
break;
127+
case Error::INTEGRATION_ERROR:
128+
case Error::UNITIALIZED_MODEL:
129+
case Error::UNSUPPORTED_PROTOCOL:
130+
case Error::PROTOCOL_WARNING:
131+
std::cerr << err.to_string() << std::endl;
132+
return make_unexpected<ProtocolError>( ProtocolError(std::move(err) ) );
133+
default:
134+
throw err;
135+
}
136+
assert (false && "Unreachable code reached in error_or_warning");
137+
}
138+
58139
/**
59-
* @brief Initialize the BMI protocol from a set of key/value properties
140+
* @brief Run the BMI protocol against the given model
60141
*
61-
* @param properties
142+
* Execute the logic of the protocol with the provided context and model.
143+
* It is the caller's responsibility to ensure that the model provided is
144+
* consistent with the model provided to the object's initialize() and
145+
* check_support() methods, hence the protected nature of this function.
146+
*
147+
* @param ctx Contextual information for the protocol run
148+
* @param model A shared pointer to a Bmi_Adapter object which should be
149+
* initialized before being passed to this method.
150+
*
151+
* @return expected<void, ProtocolError> May contain a ProtocolError if
152+
* the protocol fails for any reason. Errors of ProtocolError::PROTOCOL_WARNING
153+
* severity should be logged as warnings, but not cause the simulation to fail.
62154
*/
63-
virtual void initialize(const geojson::PropertyMap& properties) = 0;
155+
[[nodiscard]] virtual auto run(const ModelPtr& model, const Context& ctx) const -> expected<void, ProtocolError> = 0;
64156

65-
protected:
66-
67157
/**
68-
* @brief Constructor for subclasses to create NgenBmiProtocol objects
158+
* @brief Check if the BMI protocol is supported by the model
159+
*
160+
* It is the caller's responsibility to ensure that the model provided is
161+
* consistent with the model provided to the object's initialize() and
162+
* run() methods, hence the protected nature of this function.
69163
*
70164
* @param model A shared pointer to a Bmi_Adapter object which should be
71-
* initialized before being passed to this constructor.
165+
* initialized before being passed to this method.
166+
*
167+
* @return expected<void, ProtocolError> May contain a ProtocolError if
168+
* the protocol is not supported by the model.
72169
*/
73-
NgenBmiProtocol(std::shared_ptr<models::bmi::Bmi_Adapter> model): model(model){}
170+
[[nodiscard]] virtual expected<void, ProtocolError> check_support(const ModelPtr& model) = 0;
74171

75172
/**
76-
* @brief The Bmi_Adapter object used by the protocol
173+
* @brief Initialize the BMI protocol from a set of key/value properties
77174
*
175+
* It is the caller's responsibility to ensure that the model provided is
176+
* consistent with the model provided to the object's run() and
177+
* check_support() methods, hence the protected nature of this function.
178+
*
179+
* @param properties key/value pairs for initializing the protocol
180+
* @param model A shared pointer to a Bmi_Adapter object which should be
181+
* initialized before being passed to this method.
182+
*
183+
* @return expected<void, ProtocolError> May contain a ProtocolError if
184+
* initialization fails for any reason, since the protocol must
185+
* be effectively "optional", failed initialization results in
186+
* the protocol being disabled for the duration of the simulation.
78187
*/
79-
std::shared_ptr<models::bmi::Bmi_Adapter> model;
188+
virtual auto initialize(const ModelPtr& model, const Properties& properties) -> expected<void, ProtocolError> = 0;
80189

81190
/**
82191
* @brief Whether the protocol is supported by the model
83192
*
84193
*/
85194
bool is_supported = false;
195+
196+
/**
197+
* @brief Friend class for managing one or more protocols
198+
*
199+
* This allows the NgenBmiProtocols container class to access the protected `run()`
200+
* method. This allows the container to ensure consistent application of the
201+
* protocol with a particular bmi model instance throughout the lifecycle of a given
202+
* protocol.
203+
*
204+
*/
205+
friend class NgenBmiProtocols;
86206
};
87207

88208
}}}

0 commit comments

Comments
 (0)