Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/actions/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
37 changes: 11 additions & 26 deletions src/actions/action_jsonschema_evaluate_v1.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ class ActionJSONSchemaEvaluate_v1 : public sourcemeta::one::Action {
request_schema = std::get<std::string_view>(value);
} else if (key == "responseSchema") {
this->response_schema_ = std::get<std::string_view>(value);
} else if (key == "rpcSchema") {
this->rpc_schema_ = std::get<std::string_view>(value);
} else if (key == "errorSchema") {
this->error_schema_ = std::get<std::string_view>(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<std::string_view> matches,
Expand All @@ -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(
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/actions/action_jsonschema_evaluate_v1.h:79: this->blaze_template(this->rpc_schema_, ...) will assert/throw if rpcSchema wasn’t provided by the router (or the corresponding metapack is missing/corrupt), which would turn a request-time input-validation step into a process-terminating failure. Is that failure mode intended for runtime request handling, or should it degrade to an InvalidParams-style error?

Severity: medium

Other Locations
  • src/actions/action_jsonschema_trace_v1.h:81
  • src/actions/action_schema_search_v1.h:172
  • src/actions/action_serve_explorer_artifact_v1.h:70
  • src/actions/action_serve_schema_artifact_v1.h:74

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

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);
}

Expand Down Expand Up @@ -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 <typename Perform>
static auto
serve_post(const std::span<std::string_view> matches,
Expand Down Expand Up @@ -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_;
};
Expand Down
16 changes: 11 additions & 5 deletions src/actions/action_jsonschema_trace_v1.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,15 @@ class ActionJSONSchemaTrace_v1 : public sourcemeta::one::Action {
request_schema = std::get<std::string_view>(value);
} else if (key == "responseSchema") {
this->response_schema_ = std::get<std::string_view>(value);
} else if (key == "rpcSchema") {
this->rpc_schema_ = std::get<std::string_view>(value);
} else if (key == "errorSchema") {
this->error_schema_ = std::get<std::string_view>(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<std::string_view> matches,
Expand All @@ -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);
}

Expand Down Expand Up @@ -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_;
};
Expand Down
31 changes: 11 additions & 20 deletions src/actions/action_schema_search_v1.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef SOURCEMETA_ONE_ACTIONS_SCHEMA_SEARCH_V1_H
#define SOURCEMETA_ONE_ACTIONS_SCHEMA_SEARCH_V1_H

#include <sourcemeta/blaze/evaluator.h>
#include <sourcemeta/core/json.h>
#include <sourcemeta/core/uritemplate.h>

Expand Down Expand Up @@ -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<std::string_view>(value);
} else if (key == "rpcSchema") {
this->rpc_schema_ = std::get<std::string_view>(value);
} else if (key == "errorSchema") {
this->error_schema_ = std::get<std::string_view>(value);
}
Expand Down Expand Up @@ -160,51 +163,38 @@ 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<std::size_t>(raw_limit);
limit = static_cast<std::size_t>(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;
} else if (token == "title") {
scope |= sourcemeta::one::SearchScopeTitle;
} else if (token == "description") {
scope |= sourcemeta::one::SearchScopeDescription;
} else {
return sourcemeta::one::jsonrpc_make_error_invalid_params(request_id);
}
}
}
Expand All @@ -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_;
};

Expand Down
22 changes: 15 additions & 7 deletions src/actions/action_serve_explorer_artifact_v1.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef SOURCEMETA_ONE_ACTIONS_SERVE_EXPLORER_ARTIFACT_V1_H
#define SOURCEMETA_ONE_ACTIONS_SERVE_EXPLORER_ARTIFACT_V1_H

#include <sourcemeta/blaze/evaluator.h>
#include <sourcemeta/core/json.h>
#include <sourcemeta/core/uritemplate.h>

Expand Down Expand Up @@ -30,6 +31,8 @@ class ActionServeExplorerArtifact_v1 : public sourcemeta::one::Action {
this->artifact_ = std::get<std::string_view>(value);
} else if (key == "responseSchema") {
this->response_schema_ = std::get<std::string_view>(value);
} else if (key == "rpcSchema") {
this->rpc_schema_ = std::get<std::string_view>(value);
} else if (key == "errorSchema") {
this->error_schema_ = std::get<std::string_view>(value);
}
Expand Down Expand Up @@ -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(
Expand All @@ -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()) {
Expand All @@ -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_;
};

Expand Down
12 changes: 10 additions & 2 deletions src/actions/action_serve_schema_artifact_v1.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#ifndef SOURCEMETA_ONE_ACTIONS_SERVE_SCHEMA_ARTIFACT_V1_H
#define SOURCEMETA_ONE_ACTIONS_SERVE_SCHEMA_ARTIFACT_V1_H

#include <sourcemeta/blaze/evaluator.h>
#include <sourcemeta/core/json.h>
#include <sourcemeta/core/uritemplate.h>

Expand Down Expand Up @@ -30,6 +31,8 @@ class ActionServeSchemaArtifact_v1 : public sourcemeta::one::Action {
this->artifact_ = std::get<std::string_view>(value);
} else if (key == "responseSchema") {
this->response_schema_ = std::get<std::string_view>(value);
} else if (key == "rpcSchema") {
this->rpc_schema_ = std::get<std::string_view>(value);
} else if (key == "errorSchema") {
this->error_schema_ = std::get<std::string_view>(value);
}
Expand Down Expand Up @@ -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);
}

Expand All @@ -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_;
};

Expand Down
24 changes: 24 additions & 0 deletions src/actions/base.cc
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#include <sourcemeta/core/uri.h>

#include <sourcemeta/one/actions.h>
#include <sourcemeta/one/metapack.h>

#include <cassert> // assert
#include <exception> // std::exception
#include <optional> // std::optional
#include <string_view> // std::string_view
Expand Down Expand Up @@ -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
10 changes: 10 additions & 0 deletions src/actions/include/sourcemeta/one/actions.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#ifndef SOURCEMETA_ONE_ACTIONS_H
#define SOURCEMETA_ONE_ACTIONS_H

#include <sourcemeta/blaze/compiler.h>
#include <sourcemeta/blaze/evaluator.h>
#include <sourcemeta/core/json.h>
#include <sourcemeta/core/uritemplate.h>

Expand Down Expand Up @@ -80,6 +82,14 @@ class Action {
[[nodiscard]] auto uri_to_relative_path(const std::string_view uri) const
-> std::optional<std::filesystem::path>;

// 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_;
Expand Down
Loading
Loading