From c07e04ce84ae792588f2df2fd52e96eefbbf1704 Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Fri, 3 Feb 2023 12:07:51 +0000 Subject: [PATCH] Document RPC framework (#501) Fixes #500 --- src/rpc/README.md | 29 +++++++++++++ src/rpc/common/AnyHandler.h | 19 +++++++++ src/rpc/common/Specs.h | 25 +++++++++++ src/rpc/common/Validators.h | 84 +++++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+) create mode 100644 src/rpc/README.md diff --git a/src/rpc/README.md b/src/rpc/README.md new file mode 100644 index 00000000..b4797025 --- /dev/null +++ b/src/rpc/README.md @@ -0,0 +1,29 @@ +# Clio RPC subsystem +## Background +The RPC subsystem is where the common framework for handling incoming JSON requests is implemented. +Currently the NextGen RPC framework is a work in progress and the handlers are not yet implemented using the new common framework classes. + +## Integration plan +- Implement base framework - **done** +- Migrate handlers one by one, making them injectable, adding unit-tests - **in progress** +- Integrate all new handlers into clio in one go +- Cover the rest with unit-tests +- Release first time with new subsystem active + +## Components +See `common` subfolder. + +- **AnyHandler**: The type-erased wrapper that allows for storing different handlers in one map/vector. +- **RpcSpec/FieldSpec**: The RPC specification classes, used to specify how incoming JSON is to be validated before it's parsed and passed on to individual handler implementations. +- **Validators**: A bunch of supported validators that can be specified as requirements for each **`FieldSpec`** to make up the final **`RpcSpec`** of any given RPC handler. + +## Implementing a (NextGen) handler +See `unittests/rpc` for exmaples. + +Handlers need to fulfil the requirements specified by the **`Handler`** concept (see `rpc/common/Concepts.h`): +- Expose types: + * `Input` - The POD struct which acts as input for the handler + * `Output` - The POD struct which acts as output of a valid handler invocation +- Have a `spec()` member function returning a const reference to an **`RpcSpec`** describing the JSON input. +- Have a `process(Input)` member function that operates on `Input` POD and returns `HandlerReturnType` +- Implement `value_from` and `value_to` support using `tag_invoke` as per `boost::json` documentation for these functions. diff --git a/src/rpc/common/AnyHandler.h b/src/rpc/common/AnyHandler.h index 1dcb44d9..016da497 100644 --- a/src/rpc/common/AnyHandler.h +++ b/src/rpc/common/AnyHandler.h @@ -27,10 +27,23 @@ namespace RPCng { /** * @brief A type-erased Handler that can contain any (NextGen) RPC handler class + * + * This allows to store different handlers in one map/vector etc. + * Support for copying was added in order to allow storing in a + * map/unordered_map using the initializer_list constructor. */ class AnyHandler final { public: + /** + * @brief Type-erases any handler class. + * + * @tparam HandlerType The real type of wrapped handler class + * @tparam ProcessingStrategy A strategy that implements how processing of + * JSON is to be done + * @param handler The handler to wrap. Required to fulfil the @ref Handler + * concept. + */ template < Handler HandlerType, typename ProcessingStrategy = detail::DefaultProcessor> @@ -55,6 +68,12 @@ public: AnyHandler& operator=(AnyHandler&&) = default; + /** + * @brief Process incoming JSON by the stored handler + * + * @param value The JSON to process + * @return JSON result or @ref RPC::Status on error + */ [[nodiscard]] ReturnType process(boost::json::value const& value) const { diff --git a/src/rpc/common/Specs.h b/src/rpc/common/Specs.h index 5fa3ed83..8bfc9424 100644 --- a/src/rpc/common/Specs.h +++ b/src/rpc/common/Specs.h @@ -33,6 +33,14 @@ namespace RPCng { */ struct FieldSpec final { + /** + * @brief Construct a field specification out of a set of requirements + * + * @tparam Requirements The types of requirements @ref Requirement + * @param key The key in a JSON object that the field validates + * @param requirements The requirements, each of them have to fulfil + * the @ref Requirement concept + */ template FieldSpec(std::string const& key, Requirements&&... requirements) : validator_{detail::makeFieldValidator( @@ -41,6 +49,12 @@ struct FieldSpec final { } + /** + * @brief Validates the passed JSON value using the stored requirements + * + * @param value The JSON value to validate + * @return Nothing on success; @ref RPC::Status on error + */ [[nodiscard]] MaybeError validate(boost::json::value const& value) const; @@ -56,10 +70,21 @@ private: */ struct RpcSpec final { + /** + * @brief Construct a full RPC request specification + * + * @param fields The fields of the RPC specification @ref FieldSpec + */ RpcSpec(std::initializer_list fields) : fields_{fields} { } + /** + * @brief Validates the passed JSON value using the stored field specs + * + * @param value The JSON value to validate + * @return Nothing on success; @ref RPC::Status on error + */ [[nodiscard]] MaybeError validate(boost::json::value const& value) const; diff --git a/src/rpc/common/Validators.h b/src/rpc/common/Validators.h index b4443e85..dea427b1 100644 --- a/src/rpc/common/Validators.h +++ b/src/rpc/common/Validators.h @@ -76,10 +76,22 @@ class Section final std::vector specs; public: + /** + * @brief Construct new section validator from a list of specs + * + * @param specs List of specs @ref FieldSpec + */ explicit Section(std::initializer_list specs) : specs{specs} { } + /** + * @brief Verify that the JSON value representing the section is valid + * according to the given specs + * + * @param value The JSON value representing the outer object + * @param key The key used to retrieve the section from the outer object + */ [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const; }; @@ -99,6 +111,13 @@ struct Required final template struct Type final { + /** + * @brief Verify that the JSON value is (one) of specified type(s) + * + * @param value The JSON value representing the outer object + * @param key The key used to retrieve the tested value from the outer + * object + */ [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const { @@ -126,10 +145,23 @@ class Between final Type max_; public: + /** + * @brief Construct the validator storing min and max values + * + * @param min + * @param max + */ explicit Between(Type min, Type max) : min_{min}, max_{max} { } + /** + * @brief Verify that the JSON value is within a certain range + * + * @param value The JSON value representing the outer object + * @param key The key used to retrieve the tested value from the outer + * object + */ [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const { @@ -157,10 +189,22 @@ class EqualTo final Type original_; public: + /** + * @brief Construct the validator with stored original value + * + * @param original The original value to store + */ explicit EqualTo(Type original) : original_{original} { } + /** + * @brief Verify that the JSON value is equal to the stored original + * + * @param value The JSON value representing the outer object + * @param key The key used to retrieve the tested value from the outer + * object + */ [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const { @@ -192,10 +236,22 @@ class OneOf final std::vector options_; public: + /** + * @brief Construct the validator with stored options + * + * @param options The list of allowed options + */ explicit OneOf(std::initializer_list options) : options_{options} { } + /** + * @brief Verify that the JSON value is one of the stored options + * + * @param value The JSON value representing the outer object + * @param key The key used to retrieve the tested value from the outer + * object + */ [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const { @@ -229,11 +285,25 @@ class ValidateArrayAt final std::vector specs_; public: + /** + * @brief Constructs a validator that validates the specified element of a + * JSON array + * + * @param idx The index inside the array to validate + * @param specs The specifications to validate against + */ ValidateArrayAt(std::size_t idx, std::initializer_list specs) : idx_{idx}, specs_{specs} { } + /** + * @brief Verify that the JSON array element at given index is valid + * according the stored specs + * + * @param value The JSON value representing the outer object + * @param key The key used to retrieve the array from the outer object + */ [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const; }; @@ -247,11 +317,25 @@ class CustomValidator final validator_; public: + /** + * @brief Constructs a custom validator from any supported callable + * + * @tparam Fn The type of callable + * @param fn The callable/function object + */ template explicit CustomValidator(Fn&& fn) : validator_{std::forward(fn)} { } + /** + * @brief Verify that the JSON value is valid according to the custom + * validation function stored + * + * @param value The JSON value representing the outer object + * @param key The key used to retrieve the tested value from the outer + * object + */ [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const; };