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
54 changes: 37 additions & 17 deletions src/core/uritemplate/helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,10 @@ inline auto parse_varname(const std::string_view input, std::size_t position)
return position;
}

template <bool CheckOnly>
inline auto
parse_variable_list(const std::string_view input, std::size_t position,
std::vector<URITemplateVariableSpecification> &variables)
std::vector<URITemplateVariableSpecification> *variables)
-> std::size_t {
while (true) {
const auto start = position;
Expand All @@ -189,9 +190,9 @@ parse_variable_list(const std::string_view input, std::size_t position,
throw URITemplateParseError(position + 1);
}

const auto name = input.substr(start, position - start);
std::uint16_t length = 0;
bool explode = false;
[[maybe_unused]] const auto name = input.substr(start, position - start);
[[maybe_unused]] std::uint16_t length = 0;
[[maybe_unused]] bool explode = false;

if (position >= input.size()) {
throw URITemplateParseError(1);
Expand Down Expand Up @@ -225,14 +226,20 @@ parse_variable_list(const std::string_view input, std::size_t position,
throw URITemplateParseError(prefix_start + 1);
}

length = value;
if constexpr (!CheckOnly) {
length = value;
}
} else if (input[position] == '*') {
explode = true;
if constexpr (!CheckOnly) {
explode = true;
}
position++;
}

variables.push_back(URITemplateVariableSpecification{
.name = name, .length = length, .explode = explode});
if constexpr (!CheckOnly) {
variables->push_back(URITemplateVariableSpecification{
.name = name, .length = length, .explode = explode});
}

if (position >= input.size()) {
throw URITemplateParseError(1);
Expand All @@ -250,8 +257,10 @@ parse_variable_list(const std::string_view input, std::size_t position,
return position;
}

template <typename T>
auto parse_expression(const std::string_view input) -> URITemplateParseResult {
template <typename T, bool CheckOnly>
auto parse_expression(const std::string_view input)
-> std::conditional_t<CheckOnly, std::optional<std::size_t>,
URITemplateParseResult> {
if constexpr (std::is_same_v<T, URITemplateTokenLiteral>) {
if (input.empty() || input[0] == '{') {
return std::nullopt;
Expand All @@ -272,9 +281,13 @@ auto parse_expression(const std::string_view input) -> URITemplateParseResult {
position++;
}

return std::make_pair(
URITemplateToken{URITemplateTokenLiteral{input.substr(0, position)}},
position);
if constexpr (CheckOnly) {
return position;
} else {
return std::make_pair(
URITemplateToken{URITemplateTokenLiteral{input.substr(0, position)}},
position);
}
} else {
if (input.empty() || input[0] != '{') {
return std::nullopt;
Expand All @@ -297,10 +310,17 @@ auto parse_expression(const std::string_view input) -> URITemplateParseResult {
var_start = 1;
}

std::vector<URITemplateVariableSpecification> variables;
const auto end_position = parse_variable_list(input, var_start, variables);
return std::make_pair(URITemplateToken{T{std::move(variables)}},
end_position + 1);
if constexpr (CheckOnly) {
const auto end_position =
parse_variable_list<true>(input, var_start, nullptr);
return end_position + 1;
} else {
std::vector<URITemplateVariableSpecification> variables;
const auto end_position =
parse_variable_list<false>(input, var_start, &variables);
return std::make_pair(URITemplateToken{T{std::move(variables)}},
end_position + 1);
}
}
}

Expand Down
15 changes: 15 additions & 0 deletions src/core/uritemplate/include/sourcemeta/core/uritemplate.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,21 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplate {
/// ```
URITemplate(const std::string_view source);

/// Check whether the given string is a valid RFC 6570 URI Template
/// without building the parsed representation. For example:
///
/// ```cpp
/// #include <sourcemeta/core/uritemplate.h>
///
/// #include <cassert>
///
/// assert(sourcemeta::core::URITemplate::is_uritemplate(
/// "http://example.com/~{username}/"));
/// assert(!sourcemeta::core::URITemplate::is_uritemplate("{var"));
/// ```
[[nodiscard]] static auto is_uritemplate(std::string_view input) noexcept
-> bool;

/// Get the number of tokens in the template
[[nodiscard]] auto size() const noexcept -> std::uint64_t;

Expand Down
51 changes: 40 additions & 11 deletions src/core/uritemplate/uritemplate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,28 @@

namespace sourcemeta::core {

template <typename T>
template <typename T, bool CheckOnly>
static auto try_parse(std::string_view &remaining, std::size_t &offset,
std::vector<URITemplateToken> &tokens) -> bool {
if (auto result = parse_expression<T>(remaining)) {
tokens.emplace_back(std::move(result->first));
remaining.remove_prefix(result->second);
offset += result->second;
std::vector<URITemplateToken> *tokens) -> bool {
Copy link
Copy Markdown

@augmentcode augmentcode Bot May 14, 2026

Choose a reason for hiding this comment

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

src/core/uritemplate/uritemplate.cc:13: When instantiated with CheckOnly=true, the tokens parameter becomes unused due to the if constexpr branch, and this repo enables -Wunused-parameter, so this will likely produce a compiler warning. The same unused-parameter pattern also shows up in the check-only instantiations at the other locations listed below.

Severity: low

Other Locations
  • src/core/uritemplate/uritemplate.cc:31
  • src/core/uritemplate/helpers.h:183

Fix This in Augment

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

if (auto result = parse_expression<T, CheckOnly>(remaining)) {
if constexpr (CheckOnly) {
remaining.remove_prefix(*result);
offset += *result;
} else {
tokens->emplace_back(std::move(result->first));
remaining.remove_prefix(result->second);
offset += result->second;
}
return true;
}

return false;
}

template <typename... Ts>
template <bool CheckOnly, typename... Ts>
static auto try_parse_any(std::string_view &remaining, std::size_t &offset,
std::vector<URITemplateToken> &tokens) -> bool {
return (try_parse<Ts>(remaining, offset, tokens) || ...);
std::vector<URITemplateToken> *tokens) -> bool {
return (try_parse<Ts, CheckOnly>(remaining, offset, tokens) || ...);
}

URITemplate::URITemplate(const std::string_view source) {
Expand All @@ -33,15 +38,15 @@ URITemplate::URITemplate(const std::string_view source) {

while (!remaining.empty()) {
try {
if (!try_parse_any<URITemplateTokenReservedExpansion,
if (!try_parse_any<false, URITemplateTokenReservedExpansion,
URITemplateTokenFragmentExpansion,
URITemplateTokenLabelExpansion,
URITemplateTokenPathExpansion,
URITemplateTokenPathParameterExpansion,
URITemplateTokenQueryExpansion,
URITemplateTokenQueryContinuationExpansion,
URITemplateTokenVariable, URITemplateTokenLiteral>(
remaining, offset, this->tokens_)) {
remaining, offset, &this->tokens_)) {
break;
}
} catch (URITemplateParseError &error) {
Expand All @@ -50,6 +55,30 @@ URITemplate::URITemplate(const std::string_view source) {
}
}

auto URITemplate::is_uritemplate(const std::string_view input) noexcept
-> bool {
try {
std::string_view remaining{input};
std::size_t offset = 0;
while (!remaining.empty()) {
if (!try_parse_any<true, URITemplateTokenReservedExpansion,
URITemplateTokenFragmentExpansion,
URITemplateTokenLabelExpansion,
URITemplateTokenPathExpansion,
URITemplateTokenPathParameterExpansion,
URITemplateTokenQueryExpansion,
URITemplateTokenQueryContinuationExpansion,
URITemplateTokenVariable, URITemplateTokenLiteral>(
remaining, offset, nullptr)) {
return false;
}
}
return true;
} catch (...) {
return false;
}
}

auto URITemplate::size() const noexcept -> std::uint64_t {
return static_cast<std::uint64_t>(this->tokens_.size());
}
Expand Down
3 changes: 2 additions & 1 deletion test/uritemplate/uritemplate_parse_error_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
EXPECT_EQ(error.column(), expected_column); \
} catch (...) { \
FAIL(); \
}
} \
EXPECT_FALSE(sourcemeta::core::URITemplate::is_uritemplate(input))

TEST(URITemplate_parse_error, unclosed_brace) {
EXPECT_URITEMPLATE_PARSE_ERROR("{var", 1);
Expand Down
Loading
Loading