include/boost/burl/request_builder.hpp
100.0% Lines (18/18)
100.0% List of functions (7/7)
100.0% Branches (2/2)
Functions (7)
Function
Calls
Lines
Branches
Blocks
boost::burl::request_builder::request_builder(boost::burl::client&, boost::http::method, boost::urls::url)
:79
57x
100.0%
–
100.0%
boost::burl::request_builder::timeout(std::chrono::duration<long, std::ratio<1l, 1000000000l> >) &&
:193
2x
100.0%
–
100.0%
boost::burl::request_builder::followlocation(bool) &&
:220
3x
100.0%
–
100.0%
boost::burl::request_builder&& boost::burl::request_builder::body<char const (&) [4]>(char const (&) [4]) &&
:265
2x
100.0%
100.0%
100.0%
boost::burl::request_builder&& boost::burl::request_builder::body<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&&) &&
:265
2x
100.0%
100.0%
75.0%
boost::burl::request_builder::body(boost::burl::any_request_body) &&
:281
1x
100.0%
–
100.0%
boost::burl::request_builder::build() &&
:305
24x
100.0%
–
100.0%
| Line | Branch | TLA | Hits | Source Code |
|---|---|---|---|---|
| 1 | // | |||
| 2 | // Copyright (c) 2026 Mohammad Nejati | |||
| 3 | // | |||
| 4 | // Distributed under the Boost Software License, Version 1.0. (See accompanying | |||
| 5 | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |||
| 6 | // | |||
| 7 | // Official repository: https://github.com/cppalliance/burl | |||
| 8 | // | |||
| 9 | ||||
| 10 | #ifndef BOOST_BURL_REQUEST_BUILDER_HPP | |||
| 11 | #define BOOST_BURL_REQUEST_BUILDER_HPP | |||
| 12 | ||||
| 13 | #include <boost/burl/conversion.hpp> | |||
| 14 | #include <boost/burl/detail/config.hpp> | |||
| 15 | #include <boost/burl/request.hpp> | |||
| 16 | #include <boost/burl/response.hpp> | |||
| 17 | ||||
| 18 | #include <boost/capy/io_task.hpp> | |||
| 19 | #include <boost/http/field.hpp> | |||
| 20 | #include <boost/http/method.hpp> | |||
| 21 | #include <boost/url/url.hpp> | |||
| 22 | ||||
| 23 | #include <chrono> | |||
| 24 | #include <string_view> | |||
| 25 | #include <type_traits> | |||
| 26 | #include <utility> | |||
| 27 | ||||
| 28 | namespace boost | |||
| 29 | { | |||
| 30 | namespace burl | |||
| 31 | { | |||
| 32 | ||||
| 33 | class client; | |||
| 34 | ||||
| 35 | /** A builder for configuring and sending a request. | |||
| 36 | ||||
| 37 | Objects of this type are created by the verb | |||
| 38 | functions of @ref client, such as | |||
| 39 | @ref client::get and @ref client::post. Member | |||
| 40 | functions configure the request and return the | |||
| 41 | builder by rvalue reference, allowing calls to | |||
| 42 | be chained. The chain is finished with | |||
| 43 | @ref send, @ref as, or @ref build, which | |||
| 44 | consume the builder. | |||
| 45 | ||||
| 46 | @par Example | |||
| 47 | @code | |||
| 48 | auto r = co_await c.get("https://example.com/get") | |||
| 49 | .query("category", "shoes") | |||
| 50 | .header(http::field::accept_language, "en") | |||
| 51 | .as<json::value>(); | |||
| 52 | @endcode | |||
| 53 | ||||
| 54 | @see | |||
| 55 | @ref client, | |||
| 56 | @ref request, | |||
| 57 | @ref response. | |||
| 58 | */ | |||
| 59 | class request_builder | |||
| 60 | { | |||
| 61 | client& client_; | |||
| 62 | request request_; | |||
| 63 | ||||
| 64 | public: | |||
| 65 | /** Constructor. | |||
| 66 | ||||
| 67 | Constructs a builder for a request with the | |||
| 68 | provided method and URL. Ownership of the | |||
| 69 | client is not transferred; it must remain | |||
| 70 | valid until the request has been sent. | |||
| 71 | ||||
| 72 | @param client The client used to send the | |||
| 73 | request. | |||
| 74 | ||||
| 75 | @param method The method of the request. | |||
| 76 | ||||
| 77 | @param url The URL of the request. | |||
| 78 | */ | |||
| 79 | 57x | request_builder(client& client, http::method method, urls::url url) | ||
| 80 | 57x | : client_(client) | ||
| 81 | 57x | , request_{ method, std::move(url), {}, {}, {} } | ||
| 82 | { | |||
| 83 | 57x | } | ||
| 84 | ||||
| 85 | /** Append a parameter to the URL query. | |||
| 86 | ||||
| 87 | The key and value are percent-encoded. | |||
| 88 | ||||
| 89 | @par Example | |||
| 90 | @code | |||
| 91 | auto r = co_await c.get("https://example.com/get") | |||
| 92 | .query("category", "shoes") | |||
| 93 | .query("color", "blue") | |||
| 94 | .send(); | |||
| 95 | @endcode | |||
| 96 | ||||
| 97 | @param key The key of the parameter. | |||
| 98 | ||||
| 99 | @param value The value of the parameter. | |||
| 100 | ||||
| 101 | @return The builder, for chaining. | |||
| 102 | */ | |||
| 103 | BOOST_BURL_DECL | |||
| 104 | request_builder&& | |||
| 105 | query(std::string_view key, std::string_view value) &&; | |||
| 106 | ||||
| 107 | /** Set a request header. | |||
| 108 | ||||
| 109 | Any existing values for the same field are | |||
| 110 | replaced. Headers set on the request take | |||
| 111 | precedence over the default headers of the | |||
| 112 | client with the same name. | |||
| 113 | ||||
| 114 | @param field The field name constant. | |||
| 115 | ||||
| 116 | @param value The value of the field. | |||
| 117 | ||||
| 118 | @return The builder, for chaining. | |||
| 119 | */ | |||
| 120 | BOOST_BURL_DECL | |||
| 121 | request_builder&& | |||
| 122 | header(http::field field, std::string_view value) &&; | |||
| 123 | ||||
| 124 | /** Set a request header. | |||
| 125 | ||||
| 126 | Any existing values for the same field are | |||
| 127 | replaced. Headers set on the request take | |||
| 128 | precedence over the default headers of the | |||
| 129 | client with the same name. | |||
| 130 | ||||
| 131 | @param name The name of the field. | |||
| 132 | ||||
| 133 | @param value The value of the field. | |||
| 134 | ||||
| 135 | @return The builder, for chaining. | |||
| 136 | */ | |||
| 137 | BOOST_BURL_DECL | |||
| 138 | request_builder&& | |||
| 139 | header(std::string_view name, std::string_view value) &&; | |||
| 140 | ||||
| 141 | /** Set credentials for HTTP Basic authentication. | |||
| 142 | ||||
| 143 | Sets the `Authorization` header of this | |||
| 144 | request to the Basic scheme with the | |||
| 145 | provided credentials, overriding any default | |||
| 146 | set on the client. | |||
| 147 | ||||
| 148 | Credentials are not sent when a redirect | |||
| 149 | leads to a different origin, unless | |||
| 150 | @ref client::config::unrestricted_auth is | |||
| 151 | enabled. | |||
| 152 | ||||
| 153 | @param user The username. | |||
| 154 | ||||
| 155 | @param pass The password. | |||
| 156 | ||||
| 157 | @return The builder, for chaining. | |||
| 158 | */ | |||
| 159 | BOOST_BURL_DECL | |||
| 160 | request_builder&& | |||
| 161 | basic_auth(std::string_view user, std::string_view pass) &&; | |||
| 162 | ||||
| 163 | /** Set a token for HTTP Bearer authentication. | |||
| 164 | ||||
| 165 | Sets the `Authorization` header of this | |||
| 166 | request to the Bearer scheme with the | |||
| 167 | provided token, overriding any default set | |||
| 168 | on the client. | |||
| 169 | ||||
| 170 | The token is not sent when a redirect leads | |||
| 171 | to a different origin, unless | |||
| 172 | @ref client::config::unrestricted_auth is | |||
| 173 | enabled. | |||
| 174 | ||||
| 175 | @param token The bearer token. | |||
| 176 | ||||
| 177 | @return The builder, for chaining. | |||
| 178 | */ | |||
| 179 | BOOST_BURL_DECL | |||
| 180 | request_builder&& | |||
| 181 | bearer_auth(std::string_view token) &&; | |||
| 182 | ||||
| 183 | /** Set a timeout for the entire operation. | |||
| 184 | ||||
| 185 | Overrides @ref client::config::timeout for | |||
| 186 | this request. | |||
| 187 | ||||
| 188 | @param dur The timeout duration. | |||
| 189 | ||||
| 190 | @return The builder, for chaining. | |||
| 191 | */ | |||
| 192 | request_builder&& | |||
| 193 | 2x | timeout(request::options::clock::duration dur) && | ||
| 194 | { | |||
| 195 | 2x | request_.options.timeout = dur; | ||
| 196 | 2x | return std::move(*this); | ||
| 197 | } | |||
| 198 | ||||
| 199 | /** Set whether redirect responses are followed. | |||
| 200 | ||||
| 201 | Overrides | |||
| 202 | @ref client::config::followlocation for | |||
| 203 | this request. When disabled, the redirect | |||
| 204 | response is returned as-is; its `Location` | |||
| 205 | header can be inspected through the | |||
| 206 | @ref response. | |||
| 207 | ||||
| 208 | @par Example | |||
| 209 | @code | |||
| 210 | auto [ec, r] = co_await c.get("https://example.com/moved") | |||
| 211 | .followlocation(false) | |||
| 212 | .send(); | |||
| 213 | @endcode | |||
| 214 | ||||
| 215 | @param enable `true` to follow redirects. | |||
| 216 | ||||
| 217 | @return The builder, for chaining. | |||
| 218 | */ | |||
| 219 | request_builder&& | |||
| 220 | 3x | followlocation(bool enable) && | ||
| 221 | { | |||
| 222 | 3x | request_.options.followlocation = enable; | ||
| 223 | 3x | return std::move(*this); | ||
| 224 | } | |||
| 225 | ||||
| 226 | /** Set the request body. | |||
| 227 | ||||
| 228 | The value is converted to a request body by | |||
| 229 | calling `tag_invoke` with | |||
| 230 | @ref body_from_tag. | |||
| 231 | ||||
| 232 | The `Content-Type` of the body is used | |||
| 233 | unless the header is set explicitly on the | |||
| 234 | request. The `Content-Length`, or chunked | |||
| 235 | transfer encoding, is always derived from | |||
| 236 | the body. | |||
| 237 | ||||
| 238 | @par Example | |||
| 239 | @code | |||
| 240 | auto r = co_await c.post("https://example.com/post") | |||
| 241 | .body(json::value({ "key", "value" })) | |||
| 242 | .send(); | |||
| 243 | @endcode | |||
| 244 | ||||
| 245 | @param value The value to convert into the | |||
| 246 | body. | |||
| 247 | ||||
| 248 | @param args Additional arguments forwarded | |||
| 249 | to the conversion. | |||
| 250 | ||||
| 251 | @return The builder, for chaining. | |||
| 252 | ||||
| 253 | @see | |||
| 254 | @ref body_from_tag, | |||
| 255 | @ref RequestBody. | |||
| 256 | */ | |||
| 257 | template<class T, class... Args> | |||
| 258 | requires requires(T&& value, Args&&... args) { | |||
| 259 | tag_invoke( | |||
| 260 | body_from_tag<std::remove_cvref_t<T>>{}, | |||
| 261 | std::forward<T>(value), | |||
| 262 | std::forward<Args>(args)...); | |||
| 263 | } | |||
| 264 | request_builder&& | |||
| 265 | 4x | body(T&& value, Args&&... args) && | ||
| 266 | { | |||
| 267 |
2/2✓ Branch 2 taken 2 times.
✓ Branch 3 taken 2 times.
|
4x | request_.body = tag_invoke( | |
| 268 | body_from_tag<std::remove_cvref_t<T>>{}, | |||
| 269 | std::forward<T>(value), | |||
| 270 | std::forward<Args>(args)...); | |||
| 271 | 4x | return std::move(*this); | ||
| 272 | } | |||
| 273 | ||||
| 274 | /** Set the request body. | |||
| 275 | ||||
| 276 | @param body The body to set. | |||
| 277 | ||||
| 278 | @return The builder, for chaining. | |||
| 279 | */ | |||
| 280 | request_builder&& | |||
| 281 | 1x | body(any_request_body body) && | ||
| 282 | { | |||
| 283 | 1x | request_.body = std::move(body); | ||
| 284 | 1x | return std::move(*this); | ||
| 285 | } | |||
| 286 | ||||
| 287 | /** Return the configured request. | |||
| 288 | ||||
| 289 | The returned request can be stored and | |||
| 290 | executed later with @ref client::execute. | |||
| 291 | ||||
| 292 | @par Example | |||
| 293 | @code | |||
| 294 | burl::request req = c.post("https://example.com/post") | |||
| 295 | .header("X-Debug", "1") | |||
| 296 | .body("payload") | |||
| 297 | .build(); | |||
| 298 | ||||
| 299 | auto [ec, r] = co_await c.execute(std::move(req)); | |||
| 300 | @endcode | |||
| 301 | ||||
| 302 | @return The configured request. | |||
| 303 | */ | |||
| 304 | request | |||
| 305 | 24x | build() && | ||
| 306 | { | |||
| 307 | 24x | return std::move(request_); | ||
| 308 | } | |||
| 309 | ||||
| 310 | /** Asynchronously send the request. | |||
| 311 | ||||
| 312 | Sends the request and reads the response | |||
| 313 | status line and headers; the body is left | |||
| 314 | unread and can be consumed through the | |||
| 315 | returned @ref response. Equivalent to | |||
| 316 | passing the built request to | |||
| 317 | @ref client::execute. | |||
| 318 | ||||
| 319 | A status code of 400 or above yields an error | |||
| 320 | code equal to @ref condition::client_error or | |||
| 321 | @ref condition::server_error. The @ref response | |||
| 322 | is still returned, so the body can be retrieved. | |||
| 323 | ||||
| 324 | @par Example | |||
| 325 | @code | |||
| 326 | auto [ec, r] = co_await c.get("https://example.com").send(); | |||
| 327 | @endcode | |||
| 328 | ||||
| 329 | @return An awaitable yielding | |||
| 330 | `(error_code,response)`. | |||
| 331 | */ | |||
| 332 | BOOST_BURL_DECL | |||
| 333 | capy::io_task<response> | |||
| 334 | send() &&; | |||
| 335 | ||||
| 336 | /** Asynchronously send the request and convert the body. | |||
| 337 | ||||
| 338 | Sends the request and converts the response | |||
| 339 | body to `T` by calling `tag_invoke` with | |||
| 340 | @ref body_to_tag. | |||
| 341 | ||||
| 342 | A status code of 400 or above yields an error | |||
| 343 | code equal to @ref condition::client_error or | |||
| 344 | @ref condition::server_error. The body is | |||
| 345 | still read and converted, but conversion errors | |||
| 346 | are masked by the status error. | |||
| 347 | ||||
| 348 | To examine the status code before consuming the | |||
| 349 | body, use @ref send instead. | |||
| 350 | ||||
| 351 | @par Example | |||
| 352 | @code | |||
| 353 | auto [ec, body] = co_await c.get("https://example.com") | |||
| 354 | .try_as<std::string>(); | |||
| 355 | ||||
| 356 | if(ec == burl::condition::client_error) | |||
| 357 | { | |||
| 358 | std::cerr << ec.message() << '\n'; // e.g. HTTP 404 Not Found | |||
| 359 | std::cerr << body << '\n'; | |||
| 360 | } | |||
| 361 | @endcode | |||
| 362 | ||||
| 363 | @tparam T The type to convert the body to. | |||
| 364 | ||||
| 365 | @param args Additional arguments forwarded | |||
| 366 | to the conversion. | |||
| 367 | ||||
| 368 | @return An awaitable yielding | |||
| 369 | `(error_code,T)`. | |||
| 370 | ||||
| 371 | @see | |||
| 372 | @ref body_to_tag, | |||
| 373 | @ref response::try_as. | |||
| 374 | */ | |||
| 375 | template<class T, class... Args> | |||
| 376 | capy::io_task<T> | |||
| 377 | try_as(Args&&... args) && | |||
| 378 | { | |||
| 379 | return try_as_impl<T>(std::move(*this), std::forward<Args>(args)...); | |||
| 380 | } | |||
| 381 | ||||
| 382 | /** Asynchronously send the request and convert the body. | |||
| 383 | ||||
| 384 | Sends the request and converts the response | |||
| 385 | body to `T` by calling `tag_invoke` with | |||
| 386 | @ref body_to_tag. | |||
| 387 | ||||
| 388 | A status code of 400 or above throws without | |||
| 389 | reading the body. Use @ref try_as to inspect | |||
| 390 | an error response. | |||
| 391 | ||||
| 392 | @par Example | |||
| 393 | @code | |||
| 394 | auto r = co_await c.get("https://example.com") | |||
| 395 | .as<std::string>(); | |||
| 396 | @endcode | |||
| 397 | ||||
| 398 | @throw std::system_error | |||
| 399 | The request, status code, or conversion failed. | |||
| 400 | ||||
| 401 | @tparam T The type to convert the body to. | |||
| 402 | ||||
| 403 | @param args Additional arguments forwarded | |||
| 404 | to the conversion. | |||
| 405 | ||||
| 406 | @return An awaitable yielding the converted | |||
| 407 | body. | |||
| 408 | ||||
| 409 | @see | |||
| 410 | @ref body_to_tag, | |||
| 411 | @ref response::as. | |||
| 412 | */ | |||
| 413 | template<class T, class... Args> | |||
| 414 | capy::task<T> | |||
| 415 | as(Args&&... args) && | |||
| 416 | { | |||
| 417 | return as_impl<T>(std::move(*this), std::forward<Args>(args)...); | |||
| 418 | } | |||
| 419 | ||||
| 420 | private: | |||
| 421 | template<class T, class... Args> | |||
| 422 | static capy::task<T> | |||
| 423 | as_impl(request_builder rb, Args... args) | |||
| 424 | { | |||
| 425 | auto [ec, resp] = co_await std::move(rb).send(); | |||
| 426 | if(ec) | |||
| 427 | throw std::system_error(ec); | |||
| 428 | ||||
| 429 | auto [bec, body] = co_await resp.template try_as<T>(std::move(args)...); | |||
| 430 | if(bec) | |||
| 431 | throw std::system_error(bec); | |||
| 432 | ||||
| 433 | co_return std::move(body); | |||
| 434 | } | |||
| 435 | ||||
| 436 | template<class T, class... Args> | |||
| 437 | static capy::io_task<T> | |||
| 438 | try_as_impl(request_builder rb, Args... args) | |||
| 439 | { | |||
| 440 | auto [ec1, resp] = co_await std::move(rb).send(); | |||
| 441 | if(ec1 && ec1 != condition::client_error && ec1 != condition::server_error) | |||
| 442 | co_return { ec1, {} }; | |||
| 443 | ||||
| 444 | // On a status error keep ec1; otherwise surface the conversion error. | |||
| 445 | auto [ec2, body] = co_await resp.template try_as<T>(std::move(args)...); | |||
| 446 | co_return { ec1 ? ec1 : ec2, std::move(body) }; | |||
| 447 | } | |||
| 448 | }; | |||
| 449 | ||||
| 450 | } // namespace burl | |||
| 451 | } // namespace boost | |||
| 452 | ||||
| 453 | #endif | |||
| 454 |