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
|