Skip to content

Commit 5b86daf

Browse files
etrclaude
andcommitted
http_endpoint: decompose string ctor under the CCN 10 bar (22 -> 7)
http_endpoint::http_endpoint(const string&, bool, bool, bool) was a 75-line registration-time URL parser doing case-insensitive lowercase, slash normalization, per-piece dispatch between non-registration / literal / parameter modes, and optional regex compilation. CCN 22. Decomposed into private member helpers: normalize_url_complete trim trailing '/', ensure leading '/' process_url_part per-iteration mode dispatch append_non_registration_part verbatim push to url_pieces append_literal_url_part literal piece, respect '^' anchor append_parameter_url_part "{name}" / "{name|regex}" parse compile_regex_url trailing '$' + std::regex compile The ctor itself drops to CCN 7: throw-if-regex-without-registration, seed url_normalized, lowercase if CASE_INSENSITIVE, normalize slashes, tokenize, loop over parts calling process_url_part, compile_regex if requested. Every new helper sits at CCN <= 7. Behaviour preserved verbatim: the leading-'^' anchor on the first literal piece still REPLACES url_normalized's seed (rather than appending), the parameter-mode validation still throws on parts of size <3 or missing braces, and compile_regex_url still throws via the same std::regex::extended | icase | nosubs path. Verified locally: full `make check`. scripts/check-complexity.sh CCN_MAX ratcheted 23 -> 22 (the new worst offender is webserver_impl::post_iterator at CCN 21). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent adba72d commit 5b86daf

3 files changed

Lines changed: 86 additions & 57 deletions

File tree

scripts/check-complexity.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
set -euo pipefail
2828

2929
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
30-
CCN_MAX="${CCN_MAX:-23}"
30+
CCN_MAX="${CCN_MAX:-22}"
3131

3232
# Prefer the standalone `lizard` entrypoint if it's on PATH; fall back to
3333
# `python3 -m lizard` which is what `pip install --user lizard` produces

src/detail/http_endpoint.cpp

Lines changed: 71 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -42,80 +42,95 @@ namespace detail {
4242
http_endpoint::~http_endpoint() {
4343
}
4444

45+
void http_endpoint::normalize_url_complete() {
46+
if (url_complete[url_complete.size() - 1] == '/') {
47+
url_complete = url_complete.substr(0, url_complete.size() - 1);
48+
}
49+
if (url_complete[0] != '/') {
50+
url_complete = "/" + url_complete;
51+
}
52+
}
53+
54+
void http_endpoint::append_non_registration_part(const string& part, bool& first) {
55+
url_normalized += (first ? "" : "/") + part;
56+
first = false;
57+
url_pieces.push_back(part);
58+
}
59+
60+
void http_endpoint::append_literal_url_part(const string& part, bool& first) {
61+
if (first) {
62+
// First piece: respect a leading '^' anchor verbatim by replacing
63+
// (not appending to) url_normalized's seed prefix.
64+
url_normalized = (part[0] == '^' ? "" : url_normalized) + part;
65+
first = false;
66+
} else {
67+
url_normalized += "/" + part;
68+
}
69+
url_pieces.push_back(part);
70+
}
71+
72+
void http_endpoint::append_parameter_url_part(const string& part,
73+
unsigned int i, bool& first) {
74+
if (part.size() < 3 || part[0] != '{' || part[part.size() - 1] != '}') {
75+
throw std::invalid_argument("Bad URL format");
76+
}
77+
// {name} or {name|regex}: split on the optional '|'.
78+
std::string::size_type bar = part.find_first_of('|');
79+
const bool has_regex = bar != string::npos;
80+
url_pars.push_back(part.substr(1, has_regex ? bar - 1 : part.size() - 2));
81+
url_normalized += (first ? "" : "/")
82+
+ (has_regex ? part.substr(bar + 1, part.size() - bar - 2) : "([^\\/]+)");
83+
first = false;
84+
chunk_positions.push_back(i);
85+
url_pieces.push_back(part);
86+
}
87+
88+
void http_endpoint::process_url_part(const vector<string>& parts,
89+
unsigned int i, bool& first, bool registration) {
90+
if (!registration) {
91+
append_non_registration_part(parts[i], first);
92+
return;
93+
}
94+
if (!parts[i].empty() && parts[i][0] != '{') {
95+
append_literal_url_part(parts[i], first);
96+
return;
97+
}
98+
append_parameter_url_part(parts[i], i, first);
99+
}
100+
101+
void http_endpoint::compile_regex_url() {
102+
url_normalized += "$";
103+
try {
104+
re_url_normalized = std::regex(url_normalized,
105+
std::regex::extended | std::regex::icase | std::regex::nosubs);
106+
} catch (std::regex_error& e) {
107+
throw std::invalid_argument("Not a valid regex in URL: " + url_normalized);
108+
}
109+
reg_compiled = true;
110+
}
111+
45112
http_endpoint::http_endpoint(const string& url, bool family, bool registration, bool use_regex):
46113
family_url(family),
47114
reg_compiled(false) {
48115
if (use_regex && !registration) {
49116
throw std::invalid_argument("Cannot use regex if not during registration");
50117
}
51-
52118
url_normalized = use_regex ? "^/" : "/";
53-
vector<string> parts;
54119

55120
#ifdef CASE_INSENSITIVE
56121
string_utilities::to_lower_copy(url, url_complete);
57122
#else
58123
url_complete = url;
59124
#endif
125+
normalize_url_complete();
60126

61-
if (url_complete[url_complete.size() - 1] == '/') {
62-
url_complete = url_complete.substr(0, url_complete.size() - 1);
63-
}
64-
65-
if (url_complete[0] != '/') {
66-
url_complete = "/" + url_complete;
67-
}
68-
69-
parts = httpserver::http::http_utils::tokenize_url(url);
70-
string buffered;
127+
auto parts = httpserver::http::http_utils::tokenize_url(url);
71128
bool first = true;
72-
73129
for (unsigned int i = 0; i < parts.size(); i++) {
74-
if (!registration) {
75-
url_normalized += (first ? "" : "/") + parts[i];
76-
first = false;
77-
78-
url_pieces.push_back(parts[i]);
79-
80-
continue;
81-
}
82-
83-
if ((parts[i] != "") && (parts[i][0] != '{')) {
84-
if (first) {
85-
url_normalized = (parts[i][0] == '^' ? "" : url_normalized) + parts[i];
86-
first = false;
87-
} else {
88-
url_normalized += "/" + parts[i];
89-
}
90-
url_pieces.push_back(parts[i]);
91-
92-
continue;
93-
}
94-
95-
if ((parts[i].size() < 3) || (parts[i][0] != '{') || (parts[i][parts[i].size() - 1] != '}')) {
96-
throw std::invalid_argument("Bad URL format");
97-
}
98-
99-
std::string::size_type bar = parts[i].find_first_of('|');
100-
url_pars.push_back(parts[i].substr(1, bar != string::npos ? bar - 1 : parts[i].size() - 2));
101-
url_normalized += (first ? "" : "/") + (bar != string::npos ? parts[i].substr(bar + 1, parts[i].size() - bar - 2) : "([^\\/]+)");
102-
103-
first = false;
104-
105-
chunk_positions.push_back(i);
106-
107-
url_pieces.push_back(parts[i]);
130+
process_url_part(parts, i, first, registration);
108131
}
109132

110-
if (use_regex) {
111-
url_normalized += "$";
112-
try {
113-
re_url_normalized = std::regex(url_normalized, std::regex::extended | std::regex::icase | std::regex::nosubs);
114-
} catch (std::regex_error& e) {
115-
throw std::invalid_argument("Not a valid regex in URL: " + url_normalized);
116-
}
117-
reg_compiled = true;
118-
}
133+
if (use_regex) compile_regex_url();
119134
}
120135

121136
http_endpoint::http_endpoint(const http_endpoint& h):

src/httpserver/detail/http_endpoint.hpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,20 @@ class http_endpoint {
186186
* Boolean indicating if the regex is compiled
187187
**/
188188
bool reg_compiled;
189+
190+
// Sub-helpers carved out of the string-ctor to keep each function
191+
// under the cyclomatic-complexity bar. process_url_part dispatches
192+
// per-iteration between the three modes (non-registration / literal
193+
// / parameter), each of which appends its own piece to url_normalized,
194+
// url_pieces, and (for parameters) url_pars + chunk_positions.
195+
void normalize_url_complete();
196+
void process_url_part(const std::vector<std::string>& parts,
197+
unsigned int i, bool& first, bool registration);
198+
void append_non_registration_part(const std::string& part, bool& first);
199+
void append_literal_url_part(const std::string& part, bool& first);
200+
void append_parameter_url_part(const std::string& part,
201+
unsigned int i, bool& first);
202+
void compile_regex_url();
189203
};
190204

191205
} // namespace detail

0 commit comments

Comments
 (0)