• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

llnl / dftracer-utils / 30074477855

24 Jul 2026 07:08AM UTC coverage: 52.727% (+0.07%) from 52.66%
30074477855

Pull #99

github

web-flow
Merge bfddb8de9 into 06bc84ec9
Pull Request #99: Support CM time_metric (NS/MS/SEC/US) across reader and viz

42091 of 103330 branches covered (40.73%)

Branch coverage included in aggregate %.

2472 of 3143 new or added lines in 65 files covered. (78.65%)

146 existing lines in 14 files now uncovered.

37235 of 47117 relevant lines covered (79.03%)

68905.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.62
/src/dftracer/utils/server/router.cpp
1
#include <dftracer/utils/server/http_request.h>
2
#include <dftracer/utils/server/http_response.h>
3
#include <dftracer/utils/server/router.h>
4

5
#include <algorithm>
6
#include <cctype>
7
#include <charconv>
8
#include <cstdlib>
9

10
namespace dftracer::utils::server {
11

12
// ============================================================================
13
// QueryParams
14
// ============================================================================
15

16
static std::string url_decode(std::string_view sv) {
418✔
17
    std::string result;
418✔
18
    result.reserve(sv.size());
418!
19
    for (std::size_t i = 0; i < sv.size(); ++i) {
2,563✔
20
        if (sv[i] == '%' && i + 2 < sv.size()) {
2,145!
21
            char hex[3] = {sv[i + 1], sv[i + 2], '\0'};
124✔
22
            char* end = nullptr;
124✔
23
            unsigned long val = std::strtoul(hex, &end, 16);
124!
24
            if (end == hex + 2) {
124!
25
                result.push_back(static_cast<char>(val));
124!
26
                i += 2;
124✔
27
                continue;
124✔
28
            }
29
        } else if (sv[i] == '+') {
2,021✔
30
            result.push_back(' ');
2!
31
            continue;
2✔
32
        }
33
        result.push_back(sv[i]);
2,019!
34
    }
1,031✔
35
    return result;
418✔
36
}
209!
37

38
QueryParams QueryParams::parse(std::string_view query) {
176✔
39
    QueryParams params;
176✔
40
    while (!query.empty()) {
390✔
41
        auto amp = query.find('&');
214✔
42
        auto pair = query.substr(0, amp);
214!
43
        query = (amp == std::string_view::npos) ? std::string_view{}
277✔
44
                                                : query.substr(amp + 1);
126!
45

46
        auto eq = pair.find('=');
214✔
47
        if (eq == std::string_view::npos) {
214✔
48
            params.params_.emplace_back(url_decode(pair), "");
10!
49
        } else {
5✔
50
            params.params_.emplace_back(url_decode(pair.substr(0, eq)),
306!
51
                                        url_decode(pair.substr(eq + 1)));
306!
52
        }
53
    }
54
    return params;
176✔
55
}
88!
56

57
std::string_view QueryParams::get(std::string_view key,
974✔
58
                                  std::string_view default_value) const {
59
    for (const auto& [k, v] : params_) {
3,514✔
60
        if (k == key) return v;
2,730✔
61
    }
62
    return default_value;
784✔
63
}
487✔
64

65
bool QueryParams::has(std::string_view key) const {
114✔
66
    for (const auto& [k, v] : params_) {
190✔
67
        if (k == key) return true;
184✔
68
    }
69
    return false;
6✔
70
}
57✔
71

72
std::string QueryParams::canonical_key() const {
36✔
73
    std::vector<const std::pair<std::string, std::string>*> sorted;
36✔
74
    sorted.reserve(params_.size());
36!
75
    for (const auto& p : params_) sorted.push_back(&p);
130✔
76
    std::sort(sorted.begin(), sorted.end(), [](const auto* a, const auto* b) {
70!
77
        if (a->first != b->first) return a->first < b->first;
96!
NEW
78
        return a->second < b->second;
×
79
    });
34✔
80
    std::string key;
36✔
81
    for (const auto* p : sorted) {
130✔
82
        key.append(p->first);
94!
83
        key.push_back('=');
94!
84
        key.append(p->second);
94!
85
        key.push_back('\x1f');
94!
86
    }
87
    return key;
54✔
88
}
36!
89

90
int QueryParams::get_int(std::string_view key, int default_value) const {
96✔
91
    auto sv = get(key);
96!
92
    if (sv.empty()) return default_value;
96✔
93
    int val = default_value;
38✔
94
    std::from_chars(sv.data(), sv.data() + sv.size(), val);
38!
95
    return val;
38✔
96
}
48✔
97

98
double QueryParams::get_double(std::string_view key,
170✔
99
                               double default_value) const {
100
    auto sv = get(key);
170!
101
    if (sv.empty()) return default_value;
170✔
102
    // std::from_chars for doubles not universally available in C++17
103
    // compilers. Use strtod as fallback.
104
    char* end = nullptr;
86✔
105
    std::string tmp(sv);
86!
106
    double val = std::strtod(tmp.c_str(), &end);
86!
107
    if (end == tmp.c_str()) return default_value;
86✔
108
    return val;
84✔
109
}
128✔
110

111
// ============================================================================
112
// Router
113
// ============================================================================
114

115
void Router::get(const std::string& path, RouteHandler handler) {
44✔
116
    routes_.push_back(Route{"GET", path, std::move(handler), {}});
44!
117
}
44✔
118

119
void Router::get(const std::string& path, RouteHandler handler, RouteDoc doc) {
418✔
120
    routes_.push_back(Route{"GET", path, std::move(handler), std::move(doc)});
418!
121
}
418✔
122

123
void Router::post(const std::string& path, RouteHandler handler) {
×
124
    routes_.push_back(Route{"POST", path, std::move(handler), {}});
×
125
}
×
126

127
void Router::post(const std::string& path, RouteHandler handler, RouteDoc doc) {
22✔
128
    routes_.push_back(Route{"POST", path, std::move(handler), std::move(doc)});
22!
129
}
22✔
130

131
namespace {
132

133
// RFC 6750: the auth scheme name is case-insensitive.
134
bool strip_bearer_prefix(std::string_view h, std::string_view& token) {
×
135
    constexpr std::string_view BEARER = "Bearer ";
×
136
    if (h.size() <= BEARER.size()) return false;
×
137
    for (std::size_t i = 0; i < BEARER.size(); ++i) {
×
138
        if (std::tolower(static_cast<unsigned char>(h[i])) !=
×
139
            std::tolower(static_cast<unsigned char>(BEARER[i])))
×
140
            return false;
×
141
    }
142
    token = h.substr(BEARER.size());
×
143
    return true;
×
144
}
145

146
}  // namespace
147

148
coro::CoroTask<HttpResponse> Router::handle(const HttpRequest& req) {
384!
149
    // Split path and query string.
150
    auto path = req.path;
64✔
151
    std::string_view query_str;
64✔
152
    auto qpos = path.find('?');
64✔
153
    if (qpos != std::string_view::npos) {
64✔
154
        query_str = path.substr(qpos + 1);
22!
155
        path = path.substr(0, qpos);
22!
156
    }
22✔
157

158
    auto params = QueryParams::parse(query_str);
64!
159

160
    // Before the token check: browsers omit Authorization from the preflight.
161
    if (req.method == "OPTIONS") {
64!
162
        co_return HttpResponse{
66!
163
            .status_code = 204,
164
            .status_text = "No Content",
1!
165
            .headers = {{"Access-Control-Allow-Methods", "GET, POST, OPTIONS"},
1!
166
                        {"Access-Control-Allow-Headers",
1!
167
                         "Authorization, "
168
                         "Content-Type, "
169
                         "X-Request-Id"},
170
                        {"Access-Control-Max-Age", "86400"}},
1!
171
            .body = ""};
1!
172
    }
173

174
    // Optional access token: accept ?token= or "Authorization: Bearer <token>".
175
    if (!auth_token_.empty()) {
63!
176
        bool ok = params.get("token") == auth_token_;
×
177
        if (!ok) {
×
178
            std::string_view token;
179
            if (strip_bearer_prefix(req.header("Authorization"), token))
×
180
                ok = token == auth_token_;
181
        }
182
        if (!ok) {
×
183
            co_return HttpResponse{.status_code = 401,
×
184
                                   .status_text = "Unauthorized",
×
185
                                   .headers = {{"Content-Type", "text/plain"}},
×
186
                                   .body = "Unauthorized"};
×
187
        }
188
    }
×
189

190
    // Match routes (exact prefix match).
191
    for (const auto& route : routes_) {
702✔
192
        if (req.method == route.method && path == route.path) {
639✔
193
            co_return co_await route.handler(req, params);
248!
194
        }
195
    }
515✔
196

197
    // Try prefix matches for parameterized routes
198
    // (e.g., "/api/v1/files/:file/info" matches "/api/v1/files/foo/info")
199
    // For now, use exact match only — parameterized routes can be added later.
200

201
    co_return HttpResponse::not_found();
1!
202
}
316!
203

204
}  // namespace dftracer::utils::server
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc