LCOV - code coverage report
Current view: top level - http/server - request_context.hpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 100.0 % 53 53
Test Date: 2026-11-10 10:38:07 Functions: 95.2 % 83 79

            Line data    Source code
       1              : #ifndef THINGER_HTTP_SERVER_REQUEST_CONTEXT_HPP
       2              : #define THINGER_HTTP_SERVER_REQUEST_CONTEXT_HPP
       3              : 
       4              : #include <cstddef>
       5              : #include <memory>
       6              : #include <optional>
       7              : #include <type_traits>
       8              : #include <utility>
       9              : #include <vector>
      10              : 
      11              : namespace thinger::http {
      12              : 
      13              : // Key of a value in a request_context: it identifies the value and fixes its type. Keys are
      14              : // told apart by their address, not by their type or name, so two values of the same type
      15              : // live under two keys, and the keys of two libraries never collide (as Go's context keys of
      16              : // an unexported type). Define each key once, with static storage duration:
      17              : //   inline constexpr http::context_key<principal> principal_key{"principal"};
      18              : template <typename T>
      19              : class context_key {
      20              :     static_assert(std::is_object_v<T> && !std::is_array_v<T> && !std::is_const_v<T> && !std::is_volatile_v<T>,
      21              :                   "context values are objects stored by value: use a non-const, non-array object type");
      22              : public:
      23              :     // `name` is only for diagnostics: values are looked up by the key itself
      24            6 :     constexpr explicit context_key(const char* name = "") noexcept : name_(name) {}
      25              : 
      26              :     // A copy would be another key: pass keys by reference
      27              :     context_key(const context_key&) = delete;
      28              :     context_key& operator=(const context_key&) = delete;
      29              : 
      30          150 :     constexpr const char* name() const noexcept { return name_; }
      31              : 
      32              : private:
      33              :     const char* name_;
      34              : };
      35              : 
      36              : // Typed values of a request (see request::context()), such as the principal an authentication
      37              : // middleware stores for the route handler: as Express res.locals, Go context.WithValue or
      38              : // Rust http::Extensions, with a value per key. Empty until a value is set, it allocates
      39              : // nothing until then. Not synchronized: a request is processed by one coroutine at a time.
      40              : //
      41              : //   middleware: req.context().set(principal_key, principal{"alice", {"devices:read"}});
      42              : //   handler:    if (const principal* who = req.context().get(principal_key)) ...
      43              : class request_context {
      44              : public:
      45        92536 :     request_context() noexcept = default;
      46         9585 :     request_context(request_context&&) noexcept = default;
      47         4062 :     request_context& operator=(request_context&&) noexcept = default;
      48       103810 :     ~request_context() = default;
      49              : 
      50              :     // Copies every value (e.g. a dispatch_options passed by copy); throws std::logic_error,
      51              :     // leaving the target unchanged, if a value is of a type that cannot be copied
      52              :     request_context(const request_context& other);
      53              :     request_context& operator=(const request_context& other);
      54              : 
      55              :     // Store a value constructed in place from `args`, replacing (and destroying) the one of
      56              :     // the key, if any; returns the value stored
      57              :     template <typename T, typename... Args>
      58          162 :     T& emplace(const context_key<T>& key, Args&&... args) {
      59          162 :         std::unique_ptr<holder_base> value = std::make_unique<holder<T>>(std::in_place, std::forward<Args>(args)...);
      60          162 :         T& stored = static_cast<holder<T>&>(*value).value;
      61          162 :         if (entry* existing = find(&key, type_tag<T>())) {
      62           12 :             existing->value = std::move(value);
      63              :         } else {
      64          150 :             entries_.push_back(entry{&key, type_tag<T>(), key.name(), std::move(value)});
      65              :         }
      66          162 :         return stored;
      67          312 :     }
      68              : 
      69              :     // Store a value, replacing (and destroying) the one of the key, if any; returns the value stored
      70              :     template <typename T, typename U = T>
      71          126 :     T& set(const context_key<T>& key, U&& value) {
      72          126 :         return emplace(key, std::forward<U>(value));
      73              :     }
      74              : 
      75              :     // The value of the key, or null if it has none
      76              :     template <typename T>
      77          195 :     T* get(const context_key<T>& key) noexcept {
      78          195 :         entry* found = find(&key, type_tag<T>());
      79          195 :         return found ? &static_cast<holder<T>&>(*found->value).value : nullptr;
      80              :     }
      81              : 
      82              :     template <typename T>
      83            9 :     const T* get(const context_key<T>& key) const noexcept {
      84            9 :         return const_cast<request_context*>(this)->get(key);
      85              :     }
      86              : 
      87              :     template <typename T>
      88            6 :     bool contains(const context_key<T>& key) const noexcept {
      89            6 :         return get(key) != nullptr;
      90              :     }
      91              : 
      92              :     // Remove the value of the key and return it (empty if the key has none)
      93              :     template <typename T>
      94            6 :     std::optional<T> take(const context_key<T>& key) {
      95            6 :         auto it = position(&key, type_tag<T>());
      96            6 :         if (it == entries_.end()) return std::nullopt;
      97            3 :         std::optional<T> value{std::move(static_cast<holder<T>&>(*it->value).value)};
      98            3 :         entries_.erase(it);
      99            3 :         return value;
     100            3 :     }
     101              : 
     102              :     // Remove (and destroy) the value of the key; false if the key has none
     103              :     template <typename T>
     104            6 :     bool erase(const context_key<T>& key) {
     105            6 :         auto it = position(&key, type_tag<T>());
     106            6 :         if (it == entries_.end()) return false;
     107            3 :         entries_.erase(it);
     108            3 :         return true;
     109              :     }
     110              : 
     111              :     // Number of values stored
     112           12 :     std::size_t size() const noexcept { return entries_.size(); }
     113           27 :     bool empty() const noexcept { return entries_.empty(); }
     114              : 
     115              :     // Remove (and destroy) every value
     116            3 :     void clear() noexcept { entries_.clear(); }
     117              : 
     118              : private:
     119              :     struct holder_base {
     120          195 :         holder_base() = default;
     121              :         holder_base(const holder_base&) = delete;
     122              :         holder_base& operator=(const holder_base&) = delete;
     123          195 :         virtual ~holder_base() = default;
     124              :         // A copy of the value, or null if its type cannot be copied
     125              :         virtual std::unique_ptr<holder_base> clone() const = 0;
     126              :     };
     127              : 
     128              :     template <typename T>
     129              :     struct holder final : holder_base {
     130              :         template <typename... Args>
     131          288 :         explicit holder(std::in_place_t, Args&&... args) : value(std::forward<Args>(args)...) {}
     132              : 
     133           39 :         std::unique_ptr<holder_base> clone() const override {
     134              :             if constexpr (std::is_copy_constructible_v<T>) {
     135           33 :                 return std::make_unique<holder<T>>(std::in_place, value);
     136              :             } else {
     137            6 :                 return nullptr;
     138              :             }
     139              :         }
     140              : 
     141              :         T value;
     142              :     };
     143              : 
     144              :     struct entry {
     145              :         const void* key;
     146              :         // Type of the value, checked too, so a value is never read with another type (e.g.
     147              :         // after a key with automatic storage is reused at the same address)
     148              :         const void* type;
     149              :         const char* name;
     150              :         std::unique_ptr<holder_base> value;
     151              :     };
     152              : 
     153              :     template <typename T>
     154          519 :     static const void* type_tag() noexcept {
     155              :         static constexpr char tag = 0;
     156          519 :         return &tag;
     157              :     }
     158              : 
     159          369 :     std::vector<entry>::iterator position(const void* key, const void* type) noexcept {
     160          369 :         auto it = entries_.begin();
     161          456 :         while (it != entries_.end() && (it->key != key || it->type != type)) ++it;
     162          369 :         return it;
     163              :     }
     164              : 
     165          357 :     entry* find(const void* key, const void* type) noexcept {
     166          357 :         auto it = position(key, type);
     167          357 :         return it == entries_.end() ? nullptr : &*it;
     168              :     }
     169              : 
     170              :     // Few values per request: a vector searched linearly (empty, it allocates nothing)
     171              :     std::vector<entry> entries_;
     172              : };
     173              : 
     174              : } // namespace thinger::http
     175              : 
     176              : #endif // THINGER_HTTP_SERVER_REQUEST_CONTEXT_HPP
        

Generated by: LCOV version 2.0-1