diff --git a/src/actions/CMakeLists.txt b/src/actions/CMakeLists.txt index 37da2e46..5cf5a653 100644 --- a/src/actions/CMakeLists.txt +++ b/src/actions/CMakeLists.txt @@ -23,9 +23,10 @@ target_link_libraries(sourcemeta_one_actions PRIVATE sourcemeta::core::jsonpoint target_link_libraries(sourcemeta_one_actions PRIVATE sourcemeta::core::io) target_link_libraries(sourcemeta_one_actions PRIVATE sourcemeta::core::time) target_link_libraries(sourcemeta_one_actions PRIVATE sourcemeta::one::shared) -target_link_libraries(sourcemeta_one_actions PRIVATE sourcemeta::one::metapack) +target_link_libraries(sourcemeta_one_actions PUBLIC sourcemeta::one::metapack) target_link_libraries(sourcemeta_one_actions PRIVATE sourcemeta::one::search) -target_link_libraries(sourcemeta_one_actions PRIVATE sourcemeta::blaze::evaluator) +target_link_libraries(sourcemeta_one_actions PUBLIC sourcemeta::blaze::evaluator) +target_link_libraries(sourcemeta_one_actions PUBLIC sourcemeta::blaze::compiler) target_link_libraries(sourcemeta_one_actions PRIVATE sourcemeta::blaze::output) if(ONE_ENTERPRISE) diff --git a/src/actions/action_jsonschema_evaluate_v1.h b/src/actions/action_jsonschema_evaluate_v1.h index 0f64502a..c5aaee96 100644 --- a/src/actions/action_jsonschema_evaluate_v1.h +++ b/src/actions/action_jsonschema_evaluate_v1.h @@ -38,13 +38,15 @@ class ActionJSONSchemaEvaluate_v1 : public sourcemeta::one::Action { request_schema = std::get(value); } else if (key == "responseSchema") { this->response_schema_ = std::get(value); + } else if (key == "rpcSchema") { + this->rpc_schema_ = std::get(value); } else if (key == "errorSchema") { this->error_schema_ = std::get(value); } }); - this->request_schema_template_ = ActionJSONSchemaEvaluate_v1::load_template( - this->base(), router.base_path(), request_schema); + this->request_schema_template_ = this->blaze_template( + request_schema, sourcemeta::blaze::Mode::FastValidation); } auto rest(const std::span matches, @@ -68,13 +70,16 @@ class ActionJSONSchemaEvaluate_v1 : public sourcemeta::one::Action { const auto *params{sourcemeta::one::jsonrpc_params(envelope)}; if (params == nullptr || !params->is_object() || - !params->defines("arguments") || !params->at("arguments").is_object()) { + !params->defines("arguments")) { return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id); } const auto &arguments{params->at("arguments")}; - if (!arguments.defines("schema") || !arguments.at("schema").is_string() || - !arguments.defines("instance")) { + // TODO: Cache the compiled template across invocations + const auto rpc_schema_template{this->blaze_template( + this->rpc_schema_, sourcemeta::blaze::Mode::FastValidation)}; + sourcemeta::blaze::Evaluator rpc_evaluator; + if (!rpc_evaluator.validate(rpc_schema_template, arguments)) { return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id); } @@ -106,27 +111,6 @@ class ActionJSONSchemaEvaluate_v1 : public sourcemeta::one::Action { } } - static auto load_template(const std::filesystem::path &base, - const std::string_view base_path, - std::string_view request_schema) - -> sourcemeta::blaze::Template { - if (!base_path.empty() && request_schema.starts_with(base_path)) { - request_schema.remove_prefix(base_path.size()); - } - if (request_schema.starts_with('/')) { - request_schema.remove_prefix(1); - } - - const auto template_path{base / "schemas" / request_schema / "%" / - "blaze-fast.metapack"}; - const auto template_json{ - sourcemeta::one::metapack_read_json(template_path)}; - assert(template_json.has_value()); - auto compiled{sourcemeta::blaze::from_json(template_json.value())}; - assert(compiled.has_value()); - return std::move(compiled.value()); - } - template static auto serve_post(const std::span matches, @@ -281,6 +265,7 @@ class ActionJSONSchemaEvaluate_v1 : public sourcemeta::one::Action { } std::string_view response_schema_; + std::string_view rpc_schema_; std::string_view error_schema_; sourcemeta::blaze::Template request_schema_template_; }; diff --git a/src/actions/action_jsonschema_trace_v1.h b/src/actions/action_jsonschema_trace_v1.h index 99ba8829..67f6b9ed 100644 --- a/src/actions/action_jsonschema_trace_v1.h +++ b/src/actions/action_jsonschema_trace_v1.h @@ -41,13 +41,15 @@ class ActionJSONSchemaTrace_v1 : public sourcemeta::one::Action { request_schema = std::get(value); } else if (key == "responseSchema") { this->response_schema_ = std::get(value); + } else if (key == "rpcSchema") { + this->rpc_schema_ = std::get(value); } else if (key == "errorSchema") { this->error_schema_ = std::get(value); } }); - this->request_schema_template_ = ActionJSONSchemaEvaluate_v1::load_template( - this->base(), router.base_path(), request_schema); + this->request_schema_template_ = this->blaze_template( + request_schema, sourcemeta::blaze::Mode::FastValidation); } auto rest(const std::span matches, @@ -70,13 +72,16 @@ class ActionJSONSchemaTrace_v1 : public sourcemeta::one::Action { const auto *params{sourcemeta::one::jsonrpc_params(envelope)}; if (params == nullptr || !params->is_object() || - !params->defines("arguments") || !params->at("arguments").is_object()) { + !params->defines("arguments")) { return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id); } const auto &arguments{params->at("arguments")}; - if (!arguments.defines("schema") || !arguments.at("schema").is_string() || - !arguments.defines("instance")) { + // TODO: Cache the compiled template across invocations + const auto rpc_schema_template{this->blaze_template( + this->rpc_schema_, sourcemeta::blaze::Mode::FastValidation)}; + sourcemeta::blaze::Evaluator rpc_evaluator; + if (!rpc_evaluator.validate(rpc_schema_template, arguments)) { return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id); } @@ -319,6 +324,7 @@ class ActionJSONSchemaTrace_v1 : public sourcemeta::one::Action { } std::string_view response_schema_; + std::string_view rpc_schema_; std::string_view error_schema_; sourcemeta::blaze::Template request_schema_template_; }; diff --git a/src/actions/action_schema_search_v1.h b/src/actions/action_schema_search_v1.h index 0092fe88..2fbca75c 100644 --- a/src/actions/action_schema_search_v1.h +++ b/src/actions/action_schema_search_v1.h @@ -1,6 +1,7 @@ #ifndef SOURCEMETA_ONE_ACTIONS_SCHEMA_SEARCH_V1_H #define SOURCEMETA_ONE_ACTIONS_SCHEMA_SEARCH_V1_H +#include #include #include @@ -32,6 +33,8 @@ class ActionSchemaSearch_v1 : public sourcemeta::one::Action { router.arguments(identifier, [this](const auto &key, const auto &value) { if (key == "responseSchema") { this->response_schema_ = std::get(value); + } else if (key == "rpcSchema") { + this->rpc_schema_ = std::get(value); } else if (key == "errorSchema") { this->error_schema_ = std::get(value); } @@ -160,42 +163,31 @@ class ActionSchemaSearch_v1 : public sourcemeta::one::Action { const auto *params{sourcemeta::one::jsonrpc_params(envelope)}; if (params == nullptr || !params->is_object() || - !params->defines("arguments") || !params->at("arguments").is_object()) { + !params->defines("arguments")) { return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id); } const auto &arguments{params->at("arguments")}; - if (!arguments.defines("q") || !arguments.at("q").is_string()) { + // TODO: Cache the compiled template across invocations + const auto rpc_schema_template{this->blaze_template( + this->rpc_schema_, sourcemeta::blaze::Mode::FastValidation)}; + sourcemeta::blaze::Evaluator evaluator; + if (!evaluator.validate(rpc_schema_template, arguments)) { return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id); } constexpr std::size_t DEFAULT_LIMIT{10}; - constexpr std::size_t MAXIMUM_LIMIT{100}; std::size_t limit{DEFAULT_LIMIT}; if (arguments.defines("limit")) { - if (!arguments.at("limit").is_integer()) { - return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id); - } - const auto raw_limit{arguments.at("limit").to_integer()}; - if (std::cmp_less(raw_limit, 1) || - std::cmp_greater(raw_limit, MAXIMUM_LIMIT)) { - return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id); - } - limit = static_cast(raw_limit); + limit = static_cast(arguments.at("limit").to_integer()); } std::uint8_t scope{sourcemeta::one::SearchScopePath | sourcemeta::one::SearchScopeTitle | sourcemeta::one::SearchScopeDescription}; if (arguments.defines("scope")) { - if (!arguments.at("scope").is_array()) { - return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id); - } scope = 0; for (const auto &item : arguments.at("scope").as_array()) { - if (!item.is_string()) { - return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id); - } const auto &token{item.to_string()}; if (token == "path") { scope |= sourcemeta::one::SearchScopePath; @@ -203,8 +195,6 @@ class ActionSchemaSearch_v1 : public sourcemeta::one::Action { scope |= sourcemeta::one::SearchScopeTitle; } else if (token == "description") { scope |= sourcemeta::one::SearchScopeDescription; - } else { - return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id); } } } @@ -218,6 +208,7 @@ class ActionSchemaSearch_v1 : public sourcemeta::one::Action { private: sourcemeta::one::SearchView search_view_; std::string_view response_schema_; + std::string_view rpc_schema_; std::string_view error_schema_; }; diff --git a/src/actions/action_serve_explorer_artifact_v1.h b/src/actions/action_serve_explorer_artifact_v1.h index eb783590..8355f596 100644 --- a/src/actions/action_serve_explorer_artifact_v1.h +++ b/src/actions/action_serve_explorer_artifact_v1.h @@ -1,6 +1,7 @@ #ifndef SOURCEMETA_ONE_ACTIONS_SERVE_EXPLORER_ARTIFACT_V1_H #define SOURCEMETA_ONE_ACTIONS_SERVE_EXPLORER_ARTIFACT_V1_H +#include #include #include @@ -30,6 +31,8 @@ class ActionServeExplorerArtifact_v1 : public sourcemeta::one::Action { this->artifact_ = std::get(value); } else if (key == "responseSchema") { this->response_schema_ = std::get(value); + } else if (key == "rpcSchema") { + this->rpc_schema_ = std::get(value); } else if (key == "errorSchema") { this->error_schema_ = std::get(value); } @@ -58,20 +61,27 @@ class ActionServeExplorerArtifact_v1 : public sourcemeta::one::Action { const auto *params{sourcemeta::one::jsonrpc_params(envelope)}; if (params == nullptr || !params->is_object() || - !params->defines("arguments") || !params->at("arguments").is_object()) { + !params->defines("arguments")) { return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id); } const auto &arguments{params->at("arguments")}; + // TODO: Cache the compiled template across invocations + const auto rpc_schema_template{this->blaze_template( + this->rpc_schema_, sourcemeta::blaze::Mode::FastValidation)}; + sourcemeta::blaze::Evaluator evaluator; + if (!evaluator.validate(rpc_schema_template, arguments)) { + return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id); + } + auto absolute_path{this->base() / "explorer"}; if (this->artifact_ == "list") { if (arguments.defines("path")) { - if (!arguments.at("path").is_string()) { - return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id); - } const auto &path{arguments.at("path").to_string()}; if (!path.empty()) { + // Defense in depth: rpc.json's `path` is just `type: string`, + // so still reject filesystem-traversal attempts before joining. const std::filesystem::path relative_path{path}; if (relative_path.is_absolute()) { return sourcemeta::one::jsonrpc_make_error_invalid_params( @@ -87,9 +97,6 @@ class ActionServeExplorerArtifact_v1 : public sourcemeta::one::Action { } } } else { - if (!arguments.defines("schema") || !arguments.at("schema").is_string()) { - return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id); - } const auto schema_path{ this->uri_to_relative_path(arguments.at("schema").to_string())}; if (!schema_path.has_value()) { @@ -116,6 +123,7 @@ class ActionServeExplorerArtifact_v1 : public sourcemeta::one::Action { private: std::string_view artifact_; std::string_view response_schema_; + std::string_view rpc_schema_; std::string_view error_schema_; }; diff --git a/src/actions/action_serve_schema_artifact_v1.h b/src/actions/action_serve_schema_artifact_v1.h index 044d6a20..fce0485d 100644 --- a/src/actions/action_serve_schema_artifact_v1.h +++ b/src/actions/action_serve_schema_artifact_v1.h @@ -1,6 +1,7 @@ #ifndef SOURCEMETA_ONE_ACTIONS_SERVE_SCHEMA_ARTIFACT_V1_H #define SOURCEMETA_ONE_ACTIONS_SERVE_SCHEMA_ARTIFACT_V1_H +#include #include #include @@ -30,6 +31,8 @@ class ActionServeSchemaArtifact_v1 : public sourcemeta::one::Action { this->artifact_ = std::get(value); } else if (key == "responseSchema") { this->response_schema_ = std::get(value); + } else if (key == "rpcSchema") { + this->rpc_schema_ = std::get(value); } else if (key == "errorSchema") { this->error_schema_ = std::get(value); } @@ -62,12 +65,16 @@ class ActionServeSchemaArtifact_v1 : public sourcemeta::one::Action { const auto *params{sourcemeta::one::jsonrpc_params(envelope)}; if (params == nullptr || !params->is_object() || - !params->defines("arguments") || !params->at("arguments").is_object()) { + !params->defines("arguments")) { return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id); } const auto &arguments{params->at("arguments")}; - if (!arguments.defines("schema") || !arguments.at("schema").is_string()) { + // TODO: Cache the compiled template across invocations + const auto rpc_schema_template{this->blaze_template( + this->rpc_schema_, sourcemeta::blaze::Mode::FastValidation)}; + sourcemeta::blaze::Evaluator evaluator; + if (!evaluator.validate(rpc_schema_template, arguments)) { return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id); } @@ -92,6 +99,7 @@ class ActionServeSchemaArtifact_v1 : public sourcemeta::one::Action { private: std::string_view artifact_; std::string_view response_schema_; + std::string_view rpc_schema_; std::string_view error_schema_; }; diff --git a/src/actions/base.cc b/src/actions/base.cc index f61e3a6e..9cf76d8c 100644 --- a/src/actions/base.cc +++ b/src/actions/base.cc @@ -1,7 +1,9 @@ #include #include +#include +#include // assert #include // std::exception #include // std::optional #include // std::string_view @@ -43,4 +45,26 @@ auto Action::schema_directory(const std::string_view uri) const return this->base_ / "schemas" / std::move(path).value() / "%"; } +auto Action::blaze_template(std::string_view schema_uri, + const sourcemeta::blaze::Mode mode) const + -> sourcemeta::blaze::Template { + if (!this->base_path_.empty() && schema_uri.starts_with(this->base_path_)) { + schema_uri.remove_prefix(this->base_path_.size()); + } + if (schema_uri.starts_with('/')) { + schema_uri.remove_prefix(1); + } + + const auto *filename{mode == sourcemeta::blaze::Mode::FastValidation + ? "blaze-fast.metapack" + : "blaze-exhaustive.metapack"}; + const auto template_path{this->base_ / "schemas" / schema_uri / "%" / + filename}; + const auto template_json{sourcemeta::one::metapack_read_json(template_path)}; + assert(template_json.has_value()); + auto compiled{sourcemeta::blaze::from_json(template_json.value())}; + assert(compiled.has_value()); + return std::move(compiled.value()); +} + } // namespace sourcemeta::one diff --git a/src/actions/include/sourcemeta/one/actions.h b/src/actions/include/sourcemeta/one/actions.h index 2cd8a9c1..f0cb0a85 100644 --- a/src/actions/include/sourcemeta/one/actions.h +++ b/src/actions/include/sourcemeta/one/actions.h @@ -1,6 +1,8 @@ #ifndef SOURCEMETA_ONE_ACTIONS_H #define SOURCEMETA_ONE_ACTIONS_H +#include +#include #include #include @@ -80,6 +82,14 @@ class Action { [[nodiscard]] auto uri_to_relative_path(const std::string_view uri) const -> std::optional; + // Loads a precompiled Blaze template from disk for the given + // self-served schema URL (e.g. an `rpcSchema` route argument). The + // mode picks `blaze-fast.metapack` (FastValidation) or + // `blaze-exhaustive.metapack` (Exhaustive). Asserts the file exists. + [[nodiscard]] auto blaze_template(std::string_view schema_uri, + sourcemeta::blaze::Mode mode) const + -> sourcemeta::blaze::Template; + private: // NOLINTNEXTLINE(cppcoreguidelines-avoid-const-or-ref-data-members) const std::filesystem::path &base_; diff --git a/src/index/generators.h b/src/index/generators.h index 18624801..8a98cfb4 100644 --- a/src/index/generators.h +++ b/src/index/generators.h @@ -688,6 +688,30 @@ struct GENERATE_URITEMPLATE_ROUTES { const auto search_response_schema{ configuration.base_path + "/self/v1/schemas/api/schemas/search/response"}; + const auto list_rpc_schema{configuration.base_path + + "/self/v1/schemas/api/list/rpc"}; + const auto dependencies_rpc_schema{ + configuration.base_path + + "/self/v1/schemas/api/schemas/dependencies/rpc"}; + const auto dependents_rpc_schema{ + configuration.base_path + + "/self/v1/schemas/api/schemas/dependents/rpc"}; + const auto health_rpc_schema{configuration.base_path + + "/self/v1/schemas/api/schemas/health/rpc"}; + const auto locations_rpc_schema{ + configuration.base_path + "/self/v1/schemas/api/schemas/locations/rpc"}; + const auto positions_rpc_schema{ + configuration.base_path + "/self/v1/schemas/api/schemas/positions/rpc"}; + const auto stats_rpc_schema{configuration.base_path + + "/self/v1/schemas/api/schemas/stats/rpc"}; + const auto metadata_rpc_schema{configuration.base_path + + "/self/v1/schemas/api/schemas/metadata/rpc"}; + const auto evaluate_rpc_schema{configuration.base_path + + "/self/v1/schemas/api/schemas/evaluate/rpc"}; + const auto trace_rpc_schema{configuration.base_path + + "/self/v1/schemas/api/schemas/trace/rpc"}; + const auto search_rpc_schema{configuration.base_path + + "/self/v1/schemas/api/schemas/search/rpc"}; const auto error_schema{configuration.base_path + "/self/v1/schemas/api/error"}; const auto mcp_request_schema{configuration.base_path + @@ -707,6 +731,7 @@ struct GENERATE_URITEMPLATE_ROUTES { const sourcemeta::core::URITemplateRouter::Argument list_arguments[] = { {"artifact", std::string_view{"directory"}}, {"responseSchema", std::string_view{list_schema}}, + {"rpcSchema", std::string_view{list_rpc_schema}}, {"errorSchema", std::string_view{error_schema}}}; router.add("/self/v1/api/list{/path*}", "list_directory", next_id++, sourcemeta::one::ACTION_TYPE_EXPLORER_ARTIFACT_V1, @@ -716,6 +741,7 @@ struct GENERATE_URITEMPLATE_ROUTES { dependencies_arguments[] = { {"artifact", std::string_view{"dependencies"}}, {"responseSchema", std::string_view{dependencies_schema}}, + {"rpcSchema", std::string_view{dependencies_rpc_schema}}, {"errorSchema", std::string_view{error_schema}}}; router.add("/self/v1/api/schemas/dependencies/{+schema}", "get_schema_dependencies", next_id++, @@ -726,6 +752,7 @@ struct GENERATE_URITEMPLATE_ROUTES { dependents_arguments[] = { {"artifact", std::string_view{"dependents"}}, {"responseSchema", std::string_view{dependents_schema}}, + {"rpcSchema", std::string_view{dependents_rpc_schema}}, {"errorSchema", std::string_view{error_schema}}}; router.add("/self/v1/api/schemas/dependents/{+schema}", "get_schema_dependents", next_id++, @@ -735,6 +762,7 @@ struct GENERATE_URITEMPLATE_ROUTES { const sourcemeta::core::URITemplateRouter::Argument health_arguments[] = { {"artifact", std::string_view{"health"}}, {"responseSchema", std::string_view{health_schema}}, + {"rpcSchema", std::string_view{health_rpc_schema}}, {"errorSchema", std::string_view{error_schema}}}; router.add("/self/v1/api/schemas/health/{+schema}", "get_schema_health", next_id++, sourcemeta::one::ACTION_TYPE_SCHEMA_ARTIFACT_V1, @@ -744,6 +772,7 @@ struct GENERATE_URITEMPLATE_ROUTES { locations_arguments[] = { {"artifact", std::string_view{"locations"}}, {"responseSchema", std::string_view{locations_schema}}, + {"rpcSchema", std::string_view{locations_rpc_schema}}, {"errorSchema", std::string_view{error_schema}}}; router.add("/self/v1/api/schemas/locations/{+schema}", "get_schema_locations", next_id++, @@ -754,6 +783,7 @@ struct GENERATE_URITEMPLATE_ROUTES { positions_arguments[] = { {"artifact", std::string_view{"positions"}}, {"responseSchema", std::string_view{positions_schema}}, + {"rpcSchema", std::string_view{positions_rpc_schema}}, {"errorSchema", std::string_view{error_schema}}}; router.add("/self/v1/api/schemas/positions/{+schema}", "get_schema_positions", next_id++, @@ -763,6 +793,7 @@ struct GENERATE_URITEMPLATE_ROUTES { const sourcemeta::core::URITemplateRouter::Argument stats_arguments[] = { {"artifact", std::string_view{"stats"}}, {"responseSchema", std::string_view{stats_schema}}, + {"rpcSchema", std::string_view{stats_rpc_schema}}, {"errorSchema", std::string_view{error_schema}}}; router.add("/self/v1/api/schemas/stats/{+schema}", "get_schema_stats", next_id++, sourcemeta::one::ACTION_TYPE_SCHEMA_ARTIFACT_V1, @@ -771,6 +802,7 @@ struct GENERATE_URITEMPLATE_ROUTES { const sourcemeta::core::URITemplateRouter::Argument metadata_arguments[] = {{"artifact", std::string_view{"schema"}}, {"responseSchema", std::string_view{metadata_schema}}, + {"rpcSchema", std::string_view{metadata_rpc_schema}}, {"errorSchema", std::string_view{error_schema}}}; router.add("/self/v1/api/schemas/metadata/{+schema}", "get_schema_metadata", next_id++, @@ -780,6 +812,7 @@ struct GENERATE_URITEMPLATE_ROUTES { const sourcemeta::core::URITemplateRouter::Argument evaluate_arguments[] = {{"requestSchema", std::string_view{evaluate_request_schema}}, {"responseSchema", std::string_view{evaluate_response_schema}}, + {"rpcSchema", std::string_view{evaluate_rpc_schema}}, {"errorSchema", std::string_view{error_schema}}}; router.add("/self/v1/api/schemas/evaluate/{+schema}", "evaluate_schema", next_id++, sourcemeta::one::ACTION_TYPE_JSONSCHEMA_EVALUATE_V1, @@ -788,6 +821,7 @@ struct GENERATE_URITEMPLATE_ROUTES { const sourcemeta::core::URITemplateRouter::Argument trace_arguments[] = { {"requestSchema", std::string_view{trace_request_schema}}, {"responseSchema", std::string_view{trace_response_schema}}, + {"rpcSchema", std::string_view{trace_rpc_schema}}, {"errorSchema", std::string_view{error_schema}}}; router.add("/self/v1/api/schemas/trace/{+schema}", "trace_schema_evaluation", next_id++, @@ -796,6 +830,7 @@ struct GENERATE_URITEMPLATE_ROUTES { const sourcemeta::core::URITemplateRouter::Argument search_arguments[] = { {"responseSchema", std::string_view{search_response_schema}}, + {"rpcSchema", std::string_view{search_rpc_schema}}, {"errorSchema", std::string_view{error_schema}}}; router.add("/self/v1/api/schemas/search", "search_schemas", next_id++, sourcemeta::one::ACTION_TYPE_SCHEMA_SEARCH_V1,