diff --git a/src/actions/action_default_v1.h b/src/actions/action_default_v1.h index 5f7a3f95..e56171b9 100644 --- a/src/actions/action_default_v1.h +++ b/src/actions/action_default_v1.h @@ -16,6 +16,9 @@ class ActionDefault_v1 : public sourcemeta::one::Action { public: + static constexpr std::string_view DESCRIPTION{ + "Default fallback action for unmatched URIs"}; + ActionDefault_v1( const std::filesystem::path &base, const sourcemeta::core::URITemplateRouterView &router, diff --git a/src/actions/action_health_check_v1.h b/src/actions/action_health_check_v1.h index 81312e21..a612c104 100644 --- a/src/actions/action_health_check_v1.h +++ b/src/actions/action_health_check_v1.h @@ -13,6 +13,9 @@ class ActionHealthCheck_v1 : public sourcemeta::one::Action { public: + static constexpr std::string_view DESCRIPTION{ + "Report the server's health status"}; + ActionHealthCheck_v1( const std::filesystem::path &base, const sourcemeta::core::URITemplateRouterView &router, diff --git a/src/actions/action_jsonschema_evaluate_v1.h b/src/actions/action_jsonschema_evaluate_v1.h index c5aaee96..918d0a7c 100644 --- a/src/actions/action_jsonschema_evaluate_v1.h +++ b/src/actions/action_jsonschema_evaluate_v1.h @@ -26,6 +26,10 @@ class ActionJSONSchemaEvaluate_v1 : public sourcemeta::one::Action { public: + static constexpr std::string_view DESCRIPTION{ + "Validate a JSON instance against a schema and return whether it " + "is valid plus any errors"}; + ActionJSONSchemaEvaluate_v1( const std::filesystem::path &base, const sourcemeta::core::URITemplateRouterView &router, diff --git a/src/actions/action_jsonschema_trace_v1.h b/src/actions/action_jsonschema_trace_v1.h index 67f6b9ed..9cbe7fd8 100644 --- a/src/actions/action_jsonschema_trace_v1.h +++ b/src/actions/action_jsonschema_trace_v1.h @@ -29,6 +29,10 @@ class ActionJSONSchemaTrace_v1 : public sourcemeta::one::Action { public: + static constexpr std::string_view DESCRIPTION{ + "Validate a JSON instance against a schema and return detailed " + "information about every step of the evaluation process"}; + ActionJSONSchemaTrace_v1( const std::filesystem::path &base, const sourcemeta::core::URITemplateRouterView &router, diff --git a/src/actions/action_mcp_v1.h b/src/actions/action_mcp_v1.h index dca8d508..865504a2 100644 --- a/src/actions/action_mcp_v1.h +++ b/src/actions/action_mcp_v1.h @@ -14,6 +14,9 @@ class ActionMCP_v1 : public sourcemeta::one::Action, public EnterpriseMCP { public: + static constexpr std::string_view DESCRIPTION{ + "Handle Model Context Protocol JSON-RPC requests"}; + ActionMCP_v1(const std::filesystem::path &base, const sourcemeta::core::URITemplateRouterView &router, const sourcemeta::core::URITemplateRouter::Identifier identifier) @@ -53,6 +56,9 @@ class ActionMCP_v1 : public sourcemeta::one::Action, public EnterpriseMCP { class ActionMCP_v1 : public sourcemeta::one::Action { public: + static constexpr std::string_view DESCRIPTION{ + "Handle Model Context Protocol JSON-RPC requests"}; + ActionMCP_v1(const std::filesystem::path &base, const sourcemeta::core::URITemplateRouterView &router, const sourcemeta::core::URITemplateRouter::Identifier identifier) diff --git a/src/actions/action_not_found_v1.h b/src/actions/action_not_found_v1.h index 29da3dcc..a90c6a20 100644 --- a/src/actions/action_not_found_v1.h +++ b/src/actions/action_not_found_v1.h @@ -13,6 +13,9 @@ class ActionNotFound_v1 : public sourcemeta::one::Action { public: + static constexpr std::string_view DESCRIPTION{ + "Return a 404 Not Found response"}; + ActionNotFound_v1( const std::filesystem::path &base, const sourcemeta::core::URITemplateRouterView &router, diff --git a/src/actions/action_schema_search_v1.h b/src/actions/action_schema_search_v1.h index 2fbca75c..0c4b08e2 100644 --- a/src/actions/action_schema_search_v1.h +++ b/src/actions/action_schema_search_v1.h @@ -24,6 +24,9 @@ class ActionSchemaSearch_v1 : public sourcemeta::one::Action { public: + static constexpr std::string_view DESCRIPTION{ + "Search for schemas by query term"}; + ActionSchemaSearch_v1( const std::filesystem::path &base, const sourcemeta::core::URITemplateRouterView &router, diff --git a/src/actions/action_serve_explorer_artifact_v1.h b/src/actions/action_serve_explorer_artifact_v1.h index 8355f596..9e19b250 100644 --- a/src/actions/action_serve_explorer_artifact_v1.h +++ b/src/actions/action_serve_explorer_artifact_v1.h @@ -21,6 +21,9 @@ class ActionServeExplorerArtifact_v1 : public sourcemeta::one::Action { public: + static constexpr std::string_view DESCRIPTION{ + "Read a navigation artifact for browsing schemas"}; + ActionServeExplorerArtifact_v1( const std::filesystem::path &base, const sourcemeta::core::URITemplateRouterView &router, diff --git a/src/actions/action_serve_schema_artifact_v1.h b/src/actions/action_serve_schema_artifact_v1.h index fce0485d..c79d0d15 100644 --- a/src/actions/action_serve_schema_artifact_v1.h +++ b/src/actions/action_serve_schema_artifact_v1.h @@ -21,6 +21,10 @@ class ActionServeSchemaArtifact_v1 : public sourcemeta::one::Action { public: + static constexpr std::string_view DESCRIPTION{ + "Look up a precomputed artifact about a specific schema by its " + "absolute URI"}; + ActionServeSchemaArtifact_v1( const std::filesystem::path &base, const sourcemeta::core::URITemplateRouterView &router, diff --git a/src/actions/action_serve_static_v1.h b/src/actions/action_serve_static_v1.h index 716d919a..e76ab23d 100644 --- a/src/actions/action_serve_static_v1.h +++ b/src/actions/action_serve_static_v1.h @@ -15,6 +15,9 @@ class ActionServeStatic_v1 : public sourcemeta::one::Action { public: + static constexpr std::string_view DESCRIPTION{ + "Serve a static asset bundled with the server"}; + ActionServeStatic_v1( const std::filesystem::path &base, const sourcemeta::core::URITemplateRouterView &router, diff --git a/src/actions/dispatch.cc b/src/actions/dispatch.cc index b6a4cc13..78a2c064 100644 --- a/src/actions/dispatch.cc +++ b/src/actions/dispatch.cc @@ -45,6 +45,24 @@ static constexpr std::array + DESCRIPTIONS{ + {SOURCEMETA_ONE_FOR_EACH_ACTION(SOURCEMETA_ONE_DEFINE_DESCRIPTION)}}; + +#undef SOURCEMETA_ONE_DEFINE_DESCRIPTION + +auto sourcemeta::one::ActionDispatcher::description( + const sourcemeta::core::URITemplateRouter::Identifier context) noexcept + -> std::string_view { + if (context >= DESCRIPTIONS.size()) { + return {}; + } + return DESCRIPTIONS[context]; +} + sourcemeta::one::ActionDispatcher::ActionDispatcher( const std::filesystem::path &base, const sourcemeta::core::URITemplateRouterView &router) diff --git a/src/actions/include/sourcemeta/one/actions.h b/src/actions/include/sourcemeta/one/actions.h index f0cb0a85..15c9e45b 100644 --- a/src/actions/include/sourcemeta/one/actions.h +++ b/src/actions/include/sourcemeta/one/actions.h @@ -118,6 +118,10 @@ class ActionDispatcher { const char *const code, std::string &&identifier, std::string &&message) const -> void; + [[nodiscard]] static auto + description(core::URITemplateRouter::Identifier context) noexcept + -> std::string_view; + private: struct Slot { std::unique_ptr instance; diff --git a/test/unit/actions/CMakeLists.txt b/test/unit/actions/CMakeLists.txt index 69ad5f33..7ef6a1e8 100644 --- a/test/unit/actions/CMakeLists.txt +++ b/test/unit/actions/CMakeLists.txt @@ -1,5 +1,6 @@ sourcemeta_googletest(NAMESPACE sourcemeta PROJECT one NAME actions SOURCES + actions_descriptions_test.cc actions_mcp_default_test.cc actions_schema_directory_test.cc actions_uri_to_relative_path_test.cc) diff --git a/test/unit/actions/actions_descriptions_test.cc b/test/unit/actions/actions_descriptions_test.cc new file mode 100644 index 00000000..3b3a79f8 --- /dev/null +++ b/test/unit/actions/actions_descriptions_test.cc @@ -0,0 +1,94 @@ +#include + +#include + +#include + +#include // std::numeric_limits +#include // std::string_view + +TEST(Actions_description, default_v1) { + EXPECT_EQ(sourcemeta::one::ActionDispatcher::description( + sourcemeta::one::ACTION_TYPE_DEFAULT_V1), + std::string_view{"Default fallback action for unmatched URIs"}); +} + +TEST(Actions_description, health_check_v1) { + EXPECT_EQ(sourcemeta::one::ActionDispatcher::description( + sourcemeta::one::ACTION_TYPE_HEALTH_CHECK_V1), + std::string_view{"Report the server's health status"}); +} + +TEST(Actions_description, not_found_v1) { + EXPECT_EQ(sourcemeta::one::ActionDispatcher::description( + sourcemeta::one::ACTION_TYPE_NOT_FOUND_V1), + std::string_view{"Return a 404 Not Found response"}); +} + +TEST(Actions_description, schema_artifact_v1) { + EXPECT_EQ(sourcemeta::one::ActionDispatcher::description( + sourcemeta::one::ACTION_TYPE_SCHEMA_ARTIFACT_V1), + std::string_view{"Look up a precomputed artifact about a " + "specific schema by its absolute URI"}); +} + +TEST(Actions_description, explorer_artifact_v1) { + EXPECT_EQ( + sourcemeta::one::ActionDispatcher::description( + sourcemeta::one::ACTION_TYPE_EXPLORER_ARTIFACT_V1), + std::string_view{"Read a navigation artifact for browsing schemas"}); +} + +TEST(Actions_description, jsonschema_evaluate_v1) { + EXPECT_EQ(sourcemeta::one::ActionDispatcher::description( + sourcemeta::one::ACTION_TYPE_JSONSCHEMA_EVALUATE_V1), + std::string_view{"Validate a JSON instance against a schema and " + "return whether it is valid plus any errors"}); +} + +TEST(Actions_description, jsonschema_trace_v1) { + EXPECT_EQ(sourcemeta::one::ActionDispatcher::description( + sourcemeta::one::ACTION_TYPE_JSONSCHEMA_TRACE_V1), + std::string_view{ + "Validate a JSON instance against a schema and return " + "detailed information about every step of the evaluation " + "process"}); +} + +TEST(Actions_description, schema_search_v1) { + EXPECT_EQ(sourcemeta::one::ActionDispatcher::description( + sourcemeta::one::ACTION_TYPE_SCHEMA_SEARCH_V1), + std::string_view{"Search for schemas by query term"}); +} + +TEST(Actions_description, serve_static_v1) { + EXPECT_EQ(sourcemeta::one::ActionDispatcher::description( + sourcemeta::one::ACTION_TYPE_SERVE_STATIC_V1), + std::string_view{"Serve a static asset bundled with the server"}); +} + +TEST(Actions_description, mcp_v1) { + EXPECT_EQ( + sourcemeta::one::ActionDispatcher::description( + sourcemeta::one::ACTION_TYPE_MCP_V1), + std::string_view{"Handle Model Context Protocol JSON-RPC requests"}); +} + +TEST(Actions_description, every_action_has_a_non_empty_description) { + for (sourcemeta::core::URITemplateRouter::Identifier context{0}; + context < sourcemeta::one::ACTION_TYPE_COUNT; ++context) { + EXPECT_FALSE( + sourcemeta::one::ActionDispatcher::description(context).empty()) + << "Action context " << context << " has an empty description"; + } +} + +TEST(Actions_description, out_of_range_returns_empty) { + EXPECT_TRUE(sourcemeta::one::ActionDispatcher::description( + sourcemeta::one::ACTION_TYPE_COUNT) + .empty()); + EXPECT_TRUE(sourcemeta::one::ActionDispatcher::description( + std::numeric_limits< + sourcemeta::core::URITemplateRouter::Identifier>::max()) + .empty()); +}