userver: userver/ugrpc/protobuf_visit.hpp Source File
Loading...
Searching...
No Matches
protobuf_visit.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/ugrpc/protobuf_visit.hpp
4/// @brief Utilities for visiting the fields of protobufs
5
6#include <shared_mutex>
7#include <string_view>
8#include <unordered_map>
9#include <unordered_set>
10#include <vector>
11
12#include <google/protobuf/stubs/common.h>
13
14#include <userver/utils/function_ref.hpp>
15#include <userver/utils/impl/internal_tag.hpp>
16#include <userver/utils/span.hpp>
17
18namespace google::protobuf {
19
20class Message;
21class Descriptor;
22class FieldDescriptor;
23
24} // namespace google::protobuf
25
26USERVER_NAMESPACE_BEGIN
27
28namespace ugrpc {
29
30using MessageVisitCallback = utils::function_ref<void(google::protobuf::Message&)>;
31
32using FieldVisitCallback =
33 utils::function_ref<void(google::protobuf::Message&, const google::protobuf::FieldDescriptor&)>;
34
35/// @brief Execute a callback for all non-empty fields of the message.
36void VisitFields(google::protobuf::Message& message, FieldVisitCallback callback);
37
38/// @brief Execute a callback for the message and its non-empty submessages.
39void VisitMessagesRecursive(google::protobuf::Message& message, MessageVisitCallback callback);
40
41/// @brief Execute a callback for all fields
42/// of the message and its non-empty submessages.
43void VisitFieldsRecursive(google::protobuf::Message& message, FieldVisitCallback callback);
44
45using DescriptorList = std::vector<const google::protobuf::Descriptor*>;
46
47using FieldDescriptorList = std::vector<const google::protobuf::FieldDescriptor*>;
48
49/// @brief Get the descriptors of fields in the message.
51
52/// @brief Get the descriptors of current and nested messages.
54
55/// @brief Find a generated type by name.
56const google::protobuf::Descriptor* FindGeneratedMessage(std::string_view name);
57
58/// @brief Find the field of a generated type by name.
59const google::protobuf::FieldDescriptor*
60FindField(const google::protobuf::Descriptor* descriptor, std::string_view field);
61
62/// @brief Base class for @ref FieldsVisitor and @ref MessagesVisitor.
63/// Provides the interface and manages the descriptor graph
64/// to enable the visitors to find all selected structures.
65template <typename Callback>
67public:
68 enum class LockBehavior {
69 /// @brief Do not take shared_mutex locks for any operation on the visitor
70 kNone = 0,
71
72 /// @brief Take shared_lock for all read operations on the visitor
73 /// and unique_lock for all Compile operations
74 kShared = 1
75 };
76
77 BaseVisitor(BaseVisitor&&) = delete;
78 BaseVisitor(const BaseVisitor&) = delete;
79
80 /// @brief Compiles the visitor for the given message type
81 /// and its dependent types
82 void Compile(const google::protobuf::Descriptor* descriptor);
83
84 /// @brief Compiles the visitor for the given message types
85 /// and their dependent types
86 void Compile(const DescriptorList& descriptors);
87
88 /// @brief Compiles the visitor for the given
89 /// generated message type and its dependent types
90 void CompileGenerated(std::string_view message_name) { Compile(FindGeneratedMessage(message_name)); }
91
92 /// @brief Compiles the visitor for the given
93 /// generated message type and their dependent types
94 void CompileGenerated(utils::span<std::string_view> message_names) {
95 DescriptorList descriptors;
96 for (const std::string_view& message_name : message_names) {
97 descriptors.push_back(FindGeneratedMessage(message_name));
98 }
99 Compile(descriptors);
100 }
101
102 /// @brief Execute a callback without recursion
103 ///
104 /// Equivalent to @ref VisitFields
105 /// but utilizes the precompilation data from @ref Compile
106 void Visit(google::protobuf::Message& message, Callback callback);
107
108 /// @brief Execute a callback recursively
109 ///
110 /// Equivalent to @ref VisitFieldsRecursive and @ref VisitMessagesRecursive
111 /// but utilizes the precompilation data from @ref Compile
112 void VisitRecursive(google::protobuf::Message& message, Callback callback);
113
114 /// @cond
115 /// Only for internal use.
117 const google::protobuf::Descriptor*,
119
120 /// Only for internal use.
122
123 /// Only for internal use.
125
126 /// Only for internal use.
127 const Dependencies& GetFieldsWithSelectedChildren(utils::impl::InternalTag) const {
128 return fields_with_selected_children_;
129 }
130
131 /// Only for internal use.
132 const Dependencies& GetReverseEdges(utils::impl::InternalTag) const { return reverse_edges_; }
133
134 /// Only for internal use.
135 const DescriptorSet& GetPropagated(utils::impl::InternalTag) const { return propagated_; }
136
137 /// Only for internal use.
138 const DescriptorSet& GetCompiled(utils::impl::InternalTag) const { return compiled_; }
139 /// @endcond
140
141protected:
142 /// @cond
143 explicit BaseVisitor(LockBehavior lock_behavior) : lock_behavior_(lock_behavior) {}
144
145 // Disallow destruction via pointer to base
146 ~BaseVisitor() = default;
147
148 /// @brief Compile one message without nested.
149 virtual void CompileOne(const google::protobuf::Descriptor& descriptor) = 0;
150
151 /// @brief Checks if the message is selected or has anything selected.
152 virtual bool IsSelected(const google::protobuf::Descriptor&) const = 0;
153
154 /// @brief Execute a callback without recursion
155 virtual void DoVisit(google::protobuf::Message& message, Callback callback) = 0;
156 /// @endcond
157
158private:
159 /// @brief Gets all submessages of the given messages.
160 DescriptorSet GetFullSubtrees(const DescriptorList& descriptors) const;
161
162 /// @brief Propagate the selection information upwards
163 void PropagateSelected(const google::protobuf::Descriptor* descriptor);
164
165 /// @brief Safe version with recursion_limit
166 void VisitRecursiveImpl(google::protobuf::Message& message, Callback callback, int recursion_limit);
167
168 std::shared_mutex mutex_;
169 const LockBehavior lock_behavior_;
170
171 Dependencies fields_with_selected_children_;
172 Dependencies reverse_edges_;
173 DescriptorSet propagated_;
174 DescriptorSet compiled_;
175};
176
177/// @brief Collects knowledge of the structure of the protobuf messages
178/// allowing for efficient loops over fields to apply a callback to the ones
179/// selected by the 'selector' function.
180///
181/// If you do not have static knowledge of the required fields, you should
182/// use @ref VisitFields or @ref VisitFieldsRecursive that are equivalent to
183/// FieldsVisitor with a `return true;` selector.
184///
185/// @warning You should not construct this at runtime as it performs significant
186/// computations in the constructor to precompile the visitors.
187/// You should create this ones at start-up.
188///
189/// Example usage: @snippet grpc/src/ugrpc/impl/protobuf_utils.cpp
190class FieldsVisitor final : public BaseVisitor<FieldVisitCallback> {
191public:
192 using Selector = utils::function_ref<
193 bool(const google::protobuf::Descriptor& descriptor, const google::protobuf::FieldDescriptor& field)>;
194
195 /// @brief Creates the visitor with the given selector
196 /// and compiles it for the message types we can find.
197 explicit FieldsVisitor(Selector selector);
198
199 /// @brief Creates the visitor with the given selector
200 /// and compiles it for the given message types and their fields recursively.
201 FieldsVisitor(Selector selector, const DescriptorList& descriptors);
202
203 /// @brief Creates the visitor with custom thread locking behavior
204 /// and the given selector for runtime compilation.
205 ///
206 /// @warning Do not use this unless you know what you are doing.
207 FieldsVisitor(Selector selector, LockBehavior lock_behavior);
208
209 /// @brief Creates the visitor with custom thread locking behavior
210 /// and the given selector; compiles it for the given message types.
211 ///
212 /// @warning Do not use this unless you know what you are doing.
213 FieldsVisitor(Selector selector, const DescriptorList& descriptors, LockBehavior lock_behavior);
214
215 /// @cond
216 /// Only for internal use.
217 const Dependencies& GetSelectedFields(utils::impl::InternalTag) const { return selected_fields_; }
218 /// @endcond
219
220private:
221 void CompileOne(const google::protobuf::Descriptor& descriptor) override;
222
223 bool IsSelected(const google::protobuf::Descriptor& descriptor) const override {
224 return selected_fields_.find(&descriptor) != selected_fields_.end();
225 }
226
227 void DoVisit(google::protobuf::Message& message, FieldVisitCallback callback) override;
228
229 Dependencies selected_fields_;
230 const Selector selector_;
231};
232
233/// @brief Collects knowledge of the structure of the protobuf messages
234/// allowing for efficient loops over nested messages to apply a callback
235/// to the ones selected by the 'selector' function.
236///
237/// If you do not have static knowledge of the required messages, you should
238/// use @ref VisitMessagesRecursive that is equivalent to
239/// MessagesVisitor with a 'return true' selector.
240///
241/// @warning You should not construct this at runtime as it performs significant
242/// computations in the constructor to precompile the visitors.
243/// You should create this ones at start-up.
244class MessagesVisitor final : public BaseVisitor<MessageVisitCallback> {
245public:
246 using Selector = utils::function_ref<bool(const google::protobuf::Descriptor& descriptor)>;
247
248 /// @brief Creates the visitor with the given selector for runtime compilation
249 /// and compiles it for the message types we can find.
250 explicit MessagesVisitor(Selector selector);
251
252 /// @brief Creates the visitor with the given selector
253 /// and compiles it for the given message types and their fields recursively.
254 MessagesVisitor(Selector selector, const DescriptorList& descriptors);
255
256 /// @brief Creates the visitor with custom thread locking behavior
257 /// and the given selector for runtime compilation.
258 ///
259 /// @warning Do not use this unless you know what you are doing.
260 MessagesVisitor(Selector selector, LockBehavior lock_behavior);
261
262 /// @brief Creates the visitor with custom thread locking behavior
263 /// and the given selector; compiles it for the given message types.
264 ///
265 /// @warning Do not use this unless you know what you are doing.
266 MessagesVisitor(Selector selector, const DescriptorList& descriptors, LockBehavior lock_behavior);
267
268 /// @cond
269 /// Only for internal use.
270 const DescriptorSet& GetSelectedMessages(utils::impl::InternalTag) const { return selected_messages_; }
271 /// @endcond
272
273private:
274 void CompileOne(const google::protobuf::Descriptor& descriptor) override;
275
276 bool IsSelected(const google::protobuf::Descriptor& descriptor) const override {
277 return selected_messages_.find(&descriptor) != selected_messages_.end();
278 }
279
280 void DoVisit(google::protobuf::Message& message, MessageVisitCallback callback) override;
281
282 DescriptorSet selected_messages_;
283 const Selector selector_;
284};
285
286} // namespace ugrpc
287
288USERVER_NAMESPACE_END