diff --git a/src/openassetio-core/CMakeLists.txt b/src/openassetio-core/CMakeLists.txt index 3ec9f0959..89a6fd090 100644 --- a/src/openassetio-core/CMakeLists.txt +++ b/src/openassetio-core/CMakeLists.txt @@ -70,6 +70,7 @@ target_sources( src/hostApi/Manager.cpp src/hostApi/ManagerFactory.cpp src/hostApi/ManagerImplementationFactoryInterface.cpp + src/hostApi/EntityReferencePager.cpp src/log/ConsoleLogger.cpp src/log/LoggerInterface.cpp src/log/SeverityFilter.cpp diff --git a/src/openassetio-core/include/openassetio/hostApi/EntityReferencePager.hpp b/src/openassetio-core/include/openassetio/hostApi/EntityReferencePager.hpp new file mode 100644 index 000000000..7f295f895 --- /dev/null +++ b/src/openassetio-core/include/openassetio/hostApi/EntityReferencePager.hpp @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2013-2023 The Foundry Visionmongers Ltd + +#pragma once + +#include + +#include +#include + +OPENASSETIO_FWD_DECLARE(managerApi, HostSession) + +namespace openassetio { +inline namespace OPENASSETIO_CORE_ABI_VERSION { +namespace hostApi { + +OPENASSETIO_DECLARE_PTR(EntityReferencePager) + +/** + * The EntityReferencePager is the Host facing representation of a + * @fqref{managerApi.PagerInterface} implementation. + * The Manager class shouldn't be directly constructed by the host. + * The EntityReferencePager allows for the retrieval and traversal of large datasets + * in a paginated manner. + * + * Due to the variance of backends, construction, `hasNext`, `get` and + * `next` may all reasonably need to perform non trivial, networked + * operations, and thus performance characteristics should not be + * assumed.singleEntityReferencePager + * + * This object falling out of scope is a signal to the manager that the + * connection query is finished. For this reason you should avoid + * keeping hold of this object for longer than necessary. + */ +class OPENASSETIO_CORE_EXPORT EntityReferencePager { + public: + OPENASSETIO_ALIAS_PTR(EntityReferencePager) + using Page = managerApi::EntityReferencePagerInterface::Page; + + [[nodiscard]] static EntityReferencePager::Ptr make( + managerApi::EntityReferencePagerInterface::Ptr pagerInterface, + managerApi::HostSessionPtr hostSession); + + /** + * EntityReferencePager cannot be copied, as each object represents a single + * paginated Query. + * Destruction of this object is tantamount to closing the query. + */ + EntityReferencePager(const EntityReferencePager&) = delete; + EntityReferencePager& operator=(const EntityReferencePager&) = delete; + EntityReferencePager(EntityReferencePager&&) noexcept = default; + EntityReferencePager& operator=(EntityReferencePager&&) noexcept = default; + ~EntityReferencePager() = default; + + /** + * Return whether or not there is more data accessible by advancing + * the page. + */ + bool hasNext(); + + /** + * Return the current page data. + */ + Page get(); + + /** + * Advance the page. + */ + void next(); + + private: + EntityReferencePager(managerApi::EntityReferencePagerInterface::Ptr pagerInterface, + managerApi::HostSessionPtr hostSession); + + managerApi::EntityReferencePagerInterface::Ptr pagerInterface_; + managerApi::HostSessionPtr hostSession_; +}; +} // namespace hostApi +} // namespace OPENASSETIO_CORE_ABI_VERSION +} // namespace openassetio diff --git a/src/openassetio-core/include/openassetio/managerApi/EntityReferencePagerInterface.hpp b/src/openassetio-core/include/openassetio/managerApi/EntityReferencePagerInterface.hpp new file mode 100644 index 000000000..e3d644596 --- /dev/null +++ b/src/openassetio-core/include/openassetio/managerApi/EntityReferencePagerInterface.hpp @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2013-2023 The Foundry Visionmongers Ltd + +#pragma once + +#include +#include + +#include +#include + +OPENASSETIO_FWD_DECLARE(managerApi, HostSession) +OPENASSETIO_FWD_DECLARE(Context) + +namespace openassetio { +inline namespace OPENASSETIO_CORE_ABI_VERSION { +namespace managerApi { + +OPENASSETIO_DECLARE_PTR(EntityReferencePagerInterface) + +/** + * Deals with the retrieval of paginated data from the backend at the + * behest of the host. + * + * The manager is expected to extend this type, and store data + * data necessary to perform the paging operations on the extended + * object, utilizing caching when possible to reduce redundant + * queries. + * + * This object does not time out until the host gives up ownership. A + * manager should implement the destructor if they wish to close any + * open connections in response to this. + * + * To support as wide array of possible backends as possible, + * OpenAssetIO places no restraints on the behaviour of this type + * concerning performance, however, it is considered friendly to + * document the performance characteristics of your Pager implementation. + */ +class OPENASSETIO_CORE_EXPORT EntityReferencePagerInterface { + public: + OPENASSETIO_ALIAS_PTR(EntityReferencePagerInterface) + using Page = std::vector; + + // Explicitly disallow copying. + EntityReferencePagerInterface() = default; + explicit EntityReferencePagerInterface(const EntityReferencePagerInterface&) = delete; + EntityReferencePagerInterface& operator=(const EntityReferencePagerInterface&) = delete; + // Allow moving. + EntityReferencePagerInterface(EntityReferencePagerInterface&&) noexcept = default; + EntityReferencePagerInterface& operator=(EntityReferencePagerInterface&&) noexcept = default; + + /** + * Manager should override destructor to be notified when query has + * finished. + */ + virtual ~EntityReferencePagerInterface() = default; + + /** + * Returns whether or not there is more data accessible by advancing + * the page. The mechanism to acquire this information is variable, + * and left up to the specifics of the backend implementation. + */ + virtual bool hasNext(const HostSessionPtr&) = 0; + + /** + * Return the current page data. + */ + virtual Page get(const HostSessionPtr&) = 0; + + /** + * Advance the page. + */ + virtual void next(const HostSessionPtr&) = 0; +}; +} // namespace managerApi +} // namespace OPENASSETIO_CORE_ABI_VERSION +} // namespace openassetio diff --git a/src/openassetio-core/src/hostApi/EntityReferencePager.cpp b/src/openassetio-core/src/hostApi/EntityReferencePager.cpp new file mode 100644 index 000000000..8952d26f5 --- /dev/null +++ b/src/openassetio-core/src/hostApi/EntityReferencePager.cpp @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2013-2023 The Foundry Visionmongers Ltd + +#include +#include + +namespace openassetio { +inline namespace OPENASSETIO_CORE_ABI_VERSION { +namespace hostApi { + +typename EntityReferencePager::Ptr EntityReferencePager::make( + managerApi::EntityReferencePagerInterfacePtr pagerInterface, + managerApi::HostSessionPtr hostSession) { + return std::shared_ptr( + new EntityReferencePager(std::move(pagerInterface), std::move(hostSession))); +} + +EntityReferencePager::EntityReferencePager( + managerApi::EntityReferencePagerInterfacePtr pagerInterface, + managerApi::HostSessionPtr hostSession) + : pagerInterface_(std::move(pagerInterface)), hostSession_(std::move(hostSession)) {} + +bool EntityReferencePager::hasNext() { return pagerInterface_->hasNext(hostSession_); } + +typename EntityReferencePager::Page EntityReferencePager::get() { + return pagerInterface_->get(hostSession_); +} + +void EntityReferencePager::next() { pagerInterface_->next(hostSession_); } + +} // namespace hostApi +} // namespace OPENASSETIO_CORE_ABI_VERSION +} // namespace openassetio diff --git a/src/openassetio-core/tests/CMakeLists.txt b/src/openassetio-core/tests/CMakeLists.txt index 6fc898990..3eeedd8b6 100644 --- a/src/openassetio-core/tests/CMakeLists.txt +++ b/src/openassetio-core/tests/CMakeLists.txt @@ -22,6 +22,7 @@ target_sources(openassetio-core-cpp-test-exe ContextTest.cpp TraitsDataTest.cpp hostApi/ManagerTest.cpp + hostApi/PagerTest.cpp managerApi/HostTest.cpp managerApi/HostSessionTest.cpp managerApi/ManagerStateBaseTest.cpp diff --git a/src/openassetio-core/tests/hostApi/PagerTest.cpp b/src/openassetio-core/tests/hostApi/PagerTest.cpp new file mode 100644 index 000000000..1decff9af --- /dev/null +++ b/src/openassetio-core/tests/hostApi/PagerTest.cpp @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2022 The Foundry Visionmongers Ltd +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +namespace openassetio { +inline namespace OPENASSETIO_CORE_ABI_VERSION { +namespace { +/** + * Mock implementation of a ManagerInterface. + * + * Used as constructor parameter to the Manager under test. + */ +struct MockManagerInterface : trompeloeil::mock_interface { + IMPLEMENT_CONST_MOCK0(identifier); + IMPLEMENT_CONST_MOCK0(displayName); + IMPLEMENT_CONST_MOCK0(info); + IMPLEMENT_MOCK2(initialize); + IMPLEMENT_CONST_MOCK3(managementPolicy); + IMPLEMENT_CONST_MOCK2(isEntityReferenceString); + IMPLEMENT_MOCK6(resolve); + IMPLEMENT_MOCK6(preflight); + IMPLEMENT_MOCK6(register_); // NOLINT(readability-identifier-naming) +}; +/** + * Mock implementation of a HostInterface. + * + * Used as constructor parameter to Host classes required as part of these tests + */ +struct MockHostInterface : trompeloeil::mock_interface { + IMPLEMENT_CONST_MOCK0(identifier); + IMPLEMENT_CONST_MOCK0(displayName); + IMPLEMENT_CONST_MOCK0(info); +}; +/** + * Mock implementation of a LoggerInterface + * + * Used as constructor parameter to Host classes required as part of these tests + */ +struct MockLoggerInterface : trompeloeil::mock_interface { + IMPLEMENT_MOCK2(log); +}; + +/** + * Fixture providing a Manager instance injected with mock dependencies. + */ +struct ManagerFixture { + const std::shared_ptr managerInterface = + std::make_shared(); + + // For convenience, to avoid casting all the time in tests. + MockManagerInterface& mockManagerInterface = + static_cast(*managerInterface); + + // Create a HostSession with our mock HostInterface + const managerApi::HostSessionPtr hostSession = managerApi::HostSession::make( + managerApi::Host::make(std::make_shared()), + std::make_shared()); +}; + +} // namespace +} // namespace OPENASSETIO_CORE_ABI_VERSION +} // namespace openassetio + +namespace { +struct MockEntityReferencePagerInterface + : trompeloeil::mock_interface { + IMPLEMENT_MOCK1(hasNext); + IMPLEMENT_MOCK1(get); + IMPLEMENT_MOCK1(next); +}; + +} // namespace + +SCENARIO("Using an EntityReferencePager") { + using trompeloeil::_; + + GIVEN("a configured EntityReferencePager") { + const openassetio::trait::TraitSet traits = {"fakeTrait", "secondFakeTrait"}; + const openassetio::ManagerFixture fixture; + + std::shared_ptr mockEntityReferencePagerInterface = + std::make_shared(); + + openassetio::hostApi::EntityReferencePager::Ptr pager = + openassetio::hostApi::EntityReferencePager::make(mockEntityReferencePagerInterface, + fixture.hostSession); + + AND_GIVEN("pagerInterface hasMore expects to be called and returns false") { + REQUIRE_CALL(*mockEntityReferencePagerInterface, hasNext(fixture.hostSession)).RETURN(false); + WHEN("pager hasMore is called") { + bool hasMoreReturn = pager->hasNext(); + THEN("the value is false") { CHECK(hasMoreReturn == false); } + } + } + AND_GIVEN("pagerInterface hasMore expects to be called and returns true") { + REQUIRE_CALL(*mockEntityReferencePagerInterface, hasNext(fixture.hostSession)).RETURN(true); + WHEN("pager hasMore is called") { + bool hasMoreReturn = pager->hasNext(); + THEN("the value is true") { CHECK(hasMoreReturn == true); } + } + } + AND_GIVEN("pagerInterface next expects to be called") { + REQUIRE_CALL(*mockEntityReferencePagerInterface, next(fixture.hostSession)); + WHEN("pager next is called") { + pager->next(); + THEN("pagerInterface next was called") {} + } + } + AND_GIVEN("pagerInterface get expects to be called and returns false") { + const std::vector testEntRefs = { + openassetio::EntityReference("One!"), openassetio::EntityReference("Two!")}; + REQUIRE_CALL(*mockEntityReferencePagerInterface, get(fixture.hostSession)) + .RETURN(testEntRefs); + WHEN("pager get is called") { + const auto getReturn = pager->get(); + THEN("the value is as expected") { CHECK(getReturn == testEntRefs); } + } + } + } +}