summaryrefslogtreecommitdiffstats
path: root/ext/protozero/include/protozero/pbf_message.hpp
blob: d7fd8b5d0d01953462800862d2f489a25e584d3b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#ifndef PROTOZERO_PBF_MESSAGE_HPP
#define PROTOZERO_PBF_MESSAGE_HPP

/*****************************************************************************

protozero - Minimalistic protocol buffer decoder and encoder in C++.

This file is from https://github.com/mapbox/protozero where you can find more
documentation.

*****************************************************************************/

/**
 * @file pbf_message.hpp
 *
 * @brief Contains the pbf_message template class.
 */

#include "pbf_reader.hpp"
#include "types.hpp"

#include <type_traits>

namespace protozero {

/**
 * This class represents a protobuf message. Either a top-level message or
 * a nested sub-message. Top-level messages can be created from any buffer
 * with a pointer and length:
 *
 * @code
 *    enum class Message : protozero::pbf_tag_type {
 *       ...
 *    };
 *
 *    std::string buffer;
 *    // fill buffer...
 *    pbf_message<Message> message{buffer.data(), buffer.size()};
 * @endcode
 *
 * Sub-messages are created using get_message():
 *
 * @code
 *    enum class SubMessage : protozero::pbf_tag_type {
 *       ...
 *    };
 *
 *    pbf_message<Message> message{...};
 *    message.next();
 *    pbf_message<SubMessage> submessage = message.get_message();
 * @endcode
 *
 * All methods of the pbf_message class except get_bytes() and get_string()
 * provide the strong exception guarantee, ie they either succeed or do not
 * change the pbf_message object they are called on. Use the get_data() method
 * instead of get_bytes() or get_string(), if you need this guarantee.
 *
 * This template class is based on the pbf_reader class and has all the same
 * methods. The difference is that whereever the pbf_reader class takes an
 * integer tag, this template class takes a tag of the template type T.
 *
 * Read the tutorial to understand how this class is used.
 */
template <typename T>
class pbf_message : public pbf_reader {

    static_assert(std::is_same<pbf_tag_type, typename std::underlying_type<T>::type>::value,
                  "T must be enum with underlying type protozero::pbf_tag_type");

public:

    /// The type of messages this class will read.
    using enum_type = T;

    /**
     * Construct a pbf_message. All arguments are forwarded to the pbf_reader
     * parent class.
     */
    template <typename... Args>
    pbf_message(Args&&... args) noexcept : // NOLINT(google-explicit-constructor, hicpp-explicit-conversions)
        pbf_reader{std::forward<Args>(args)...} {
    }

    /**
     * Set next field in the message as the current field. This is usually
     * called in a while loop:
     *
     * @code
     *    pbf_message<...> message(...);
     *    while (message.next()) {
     *        // handle field
     *    }
     * @endcode
     *
     * @returns `true` if there is a next field, `false` if not.
     * @pre There must be no current field.
     * @post If it returns `true` there is a current field now.
     */
    bool next() {
        return pbf_reader::next();
    }

    /**
     * Set next field with given tag in the message as the current field.
     * Fields with other tags are skipped. This is usually called in a while
     * loop for repeated fields:
     *
     * @code
     *    pbf_message<Example1> message{...};
     *    while (message.next(Example1::repeated_fixed64_r)) {
     *        // handle field
     *    }
     * @endcode
     *
     * or you can call it just once to get the one field with this tag:
     *
     * @code
     *    pbf_message<Example1> message{...};
     *    if (message.next(Example1::required_uint32_x)) {
     *        // handle field
     *    }
     * @endcode
     *
     * Note that this will not check the wire type. The two-argument version
     * of this function will also check the wire type.
     *
     * @returns `true` if there is a next field with this tag.
     * @pre There must be no current field.
     * @post If it returns `true` there is a current field now with the given tag.
     */
    bool next(T next_tag) {
        return pbf_reader::next(pbf_tag_type(next_tag));
    }

    /**
     * Set next field with given tag and wire type in the message as the
     * current field. Fields with other tags are skipped. This is usually
     * called in a while loop for repeated fields:
     *
     * @code
     *    pbf_message<Example1> message{...};
     *    while (message.next(Example1::repeated_fixed64_r, pbf_wire_type::varint)) {
     *        // handle field
     *    }
     * @endcode
     *
     * or you can call it just once to get the one field with this tag:
     *
     * @code
     *    pbf_message<Example1> message{...};
     *    if (message.next(Example1::required_uint32_x, pbf_wire_type::varint)) {
     *        // handle field
     *    }
     * @endcode
     *
     * Note that this will also check the wire type. The one-argument version
     * of this function will not check the wire type.
     *
     * @returns `true` if there is a next field with this tag.
     * @pre There must be no current field.
     * @post If it returns `true` there is a current field now with the given tag.
     */
    bool next(T next_tag, pbf_wire_type type) {
        return pbf_reader::next(pbf_tag_type(next_tag), type);
    }

    /**
     * The tag of the current field. The tag is the enum value for the field
     * number from the description in the .proto file.
     *
     * Call next() before calling this function to set the current field.
     *
     * @returns tag of the current field.
     * @pre There must be a current field (ie. next() must have returned `true`).
     */
    T tag() const noexcept {
        return T(pbf_reader::tag());
    }

}; // class pbf_message

} // end namespace protozero

#endif // PROTOZERO_PBF_MESSAGE_HPP