|
| 1 | +// Copyright 2024 Redpanda Data, Inc. |
| 2 | +// |
| 3 | +// Use of this software is governed by the Business Source License |
| 4 | +// included in the file licenses/BSL.md |
| 5 | +// |
| 6 | +// As of the Change Date specified in that file, in accordance with |
| 7 | +// the Business Source License, use of this software will be governed |
| 8 | +// by the Apache License, Version 2.0 |
| 9 | + |
| 10 | +#pragma once |
| 11 | + |
| 12 | +#include "bytes/iobuf.h" |
| 13 | +#include "bytes/iobuf_parser.h" |
| 14 | +#include "json/chunked_buffer.h" |
| 15 | +#include "json/writer.h" |
| 16 | + |
| 17 | +#include <rapidjson/rapidjson.h> |
| 18 | + |
| 19 | +namespace json { |
| 20 | + |
| 21 | +///\brief a json::Writer that can accept an iobuf as a String payload. |
| 22 | +template< |
| 23 | + typename OutputStream, |
| 24 | + typename SourceEncoding = json::UTF8<>, |
| 25 | + typename TargetEncoding = json::UTF8<>, |
| 26 | + unsigned writeFlags = rapidjson::kWriteDefaultFlags> |
| 27 | +class generic_iobuf_writer |
| 28 | + : public Writer<OutputStream, SourceEncoding, TargetEncoding, writeFlags> { |
| 29 | + using Base |
| 30 | + = Writer<OutputStream, SourceEncoding, TargetEncoding, writeFlags>; |
| 31 | + |
| 32 | +public: |
| 33 | + explicit generic_iobuf_writer(OutputStream& os) |
| 34 | + : Base{os} {} |
| 35 | + |
| 36 | + using Base::String; |
| 37 | + bool String(const iobuf& buf) { |
| 38 | + constexpr bool buffer_is_chunked |
| 39 | + = std::same_as<OutputStream, json::chunked_buffer>; |
| 40 | + if constexpr (buffer_is_chunked) { |
| 41 | + return write_chunked_string(buf); |
| 42 | + } else { |
| 43 | + iobuf_const_parser p{buf}; |
| 44 | + auto str = p.read_string(p.bytes_left()); |
| 45 | + return this->String(str.data(), str.size(), true); |
| 46 | + } |
| 47 | + } |
| 48 | + |
| 49 | +private: |
| 50 | + bool write_chunked_string(const iobuf& buf) { |
| 51 | + const auto last_frag = [this]() { |
| 52 | + return std::prev(this->os_->_impl.end()); |
| 53 | + }; |
| 54 | + using Ch = Base::Ch; |
| 55 | + this->Prefix(rapidjson::kStringType); |
| 56 | + const auto beg = buf.begin(); |
| 57 | + const auto end = buf.end(); |
| 58 | + const auto last = std::prev(end); |
| 59 | + Ch stashed{}; |
| 60 | + Ch* stash_pos{}; |
| 61 | + // Base::WriteString is used to JSON encode the string, and requires a |
| 62 | + // contiguous range (pointer, len), so we pass it each fragment. |
| 63 | + // |
| 64 | + // Unfortunately it also encloses the encoded fragment with double |
| 65 | + // quotes: |
| 66 | + // R"("A string made of ""fragments will need ""fixing")" |
| 67 | + // |
| 68 | + // This algorithm efficiently removes the extra quotes without |
| 69 | + // additional copying: |
| 70 | + // For each encoded fragment that is written (except the last one): |
| 71 | + // 1. Trim the suffix quote |
| 72 | + // 2. Stash the final character, and where it is to be written |
| 73 | + // 3. Drop the final character |
| 74 | + // For each encoded fragment that is written (except the first one): |
| 75 | + // 4. Restore the stashed character over the prefix-quote |
| 76 | + for (auto i = beg; i != end; ++i) { |
| 77 | + if (!Base::WriteString(i->get(), i->size())) { |
| 78 | + return false; |
| 79 | + } |
| 80 | + if (i != beg) { |
| 81 | + // 4. Restore the stashed character over the prefix-quote |
| 82 | + *stash_pos = stashed; |
| 83 | + } |
| 84 | + if (i != last) { |
| 85 | + // 1. Trim the suffix quote |
| 86 | + this->os_->_impl.trim_back(1); |
| 87 | + |
| 88 | + // 2. Stash the final character, ... |
| 89 | + auto last = last_frag(); |
| 90 | + stashed = *std::prev(last->get_current()); |
| 91 | + // 3. Drop the final character |
| 92 | + this->os_->_impl.trim_back(1); |
| 93 | + |
| 94 | + // Ensure a stable address to restore the stashed character |
| 95 | + if (last != last_frag()) { |
| 96 | + this->os_->_impl.reserve_memory(1); |
| 97 | + } |
| 98 | + // 2. ...and where it is to be written. |
| 99 | + stash_pos = last_frag()->get_current(); |
| 100 | + } |
| 101 | + } |
| 102 | + return this->EndValue(true); |
| 103 | + } |
| 104 | +}; |
| 105 | + |
| 106 | +template< |
| 107 | + typename OutputStream, |
| 108 | + typename SourceEncoding = json::UTF8<>, |
| 109 | + typename TargetEncoding = json::UTF8<>, |
| 110 | + unsigned writeFlags = rapidjson::kWriteDefaultFlags> |
| 111 | +using iobuf_writer = generic_iobuf_writer< |
| 112 | + OutputStream, |
| 113 | + SourceEncoding, |
| 114 | + TargetEncoding, |
| 115 | + writeFlags>; |
| 116 | + |
| 117 | +} // namespace json |
0 commit comments