userver: userver/utils/encoding/tskv_parser.hpp Source File
Loading...
Searching...
No Matches
tskv_parser.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/utils/encoding/tskv_parser.hpp
4/// @brief @copybrief utils::encoding::TskvParser
5
6#include <optional>
7#include <string>
8#include <string_view>
9
10USERVER_NAMESPACE_BEGIN
11
12namespace utils::encoding {
13
14/// @brief A streaming parser for the TSKV variant that userver emits.
15///
16/// **Supported syntax**
17/// 1. Records are separated by a single non-escaped `\n` character
18/// 2. A record consists of entries separated by a single `\t` character
19/// 3. The first entry SHOULD be verbatim `tskv`
20/// 4. Entries MAY have values, separated by the first non-escaped `=`
21/// 5. An entry MAY have no key-value split, in which case it all counts as key
22///
23/// **Escaping**
24/// 1. `\n`, `\t`, `\\` in keys or values SHOULD be escaped as
25/// `"\\\n"`, `"\\\t"`, `"\\\\"`
26/// 2. `=` SHOULD be escaped as `"\\="` in keys, MAY be escaped in values
27/// 3. `\r` and `\0` MAY be escaped as `"\\\r"`, `"\\\0"`
28///
29/// **Parsing process**
30/// Initialize `TskvParser` with a caller-owned string that may contain a single
31/// or multiple TSKV records. For each record, first call `SkipToRecordBegin`.
32/// Then call `ReadKey` and `ReadValue` a bunch of times.
33///
34/// For a simpler way to read TSKV records, see utils::encoding::TskvReadRecord.
35class TskvParser final {
36 public:
37 /// The status is returned once the record is complete. We ignore invalid
38 /// escaping, not reporting it as an error, trying to push through.
39 /// `nullopt` status means we should keep reading the current TSKV record.
40 enum class [[nodiscard]] RecordStatus {
41 kReachedEnd, ///< successfully read the whole record
42 kIncomplete, ///< the record ends abruptly, the service is probably
43 ///< still writing the record
44 };
45
46 explicit TskvParser(std::string_view in) noexcept;
47
48 /// @brief Skips the current record or optional invalid junk until it finds
49 /// the start of the a valid record.
50 /// @returns pointer to the start of the next record if there is one,
51 /// `nullptr` otherwise
52 /// @note `tskv\n` records are currently not supported
53 const char* SkipToRecordBegin() noexcept;
54
55 /// @brief Skips to the end of the current record, which might not be the same
56 /// as the start of the next valid record.
57 /// @note RecordStatus::kReachedEnd SHOULD
58 /// not have been returned for the current record, otherwise the behavior is
59 /// unspecified.
60 /// @note `tskv\n` records are currently not supported
62
63 /// @brief Parses the key, replacing `result` with it.
64 /// @throws std::bad_alloc only if `std::string` allocation throws
65 /// @note RecordStatus::kReachedEnd is returned specifically
66 /// on a trailing `\t\n` sequence
67 std::optional<RecordStatus> ReadKey(std::string& result);
68
69 /// @brief Parses the value, replacing `result` with it.
70 /// @throws std::bad_alloc only if `std::string` allocation throws
71 std::optional<RecordStatus> ReadValue(std::string& result);
72
73 /// @returns pointer to the data that will be read by the next operation
74 const char* GetStreamPosition() const noexcept;
75
76 private:
77 std::string_view in_;
78};
79
80} // namespace utils::encoding
81
82USERVER_NAMESPACE_END