Line data Source code
1 : //
2 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
3 : //
4 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 : //
7 : // Official repository: https://github.com/cppalliance/http
8 : //
9 :
10 : #ifndef BOOST_HTTP_JSON_JSON_SINK_HPP
11 : #define BOOST_HTTP_JSON_JSON_SINK_HPP
12 :
13 : #include <boost/http/config.hpp>
14 :
15 : #include <boost/capy/buffers.hpp>
16 : #include <boost/capy/concept/const_buffer_sequence.hpp>
17 : #include <boost/capy/ex/immediate.hpp>
18 : #include <boost/capy/io_result.hpp>
19 : #include <boost/json/stream_parser.hpp>
20 : #include <boost/json/value.hpp>
21 :
22 : namespace boost {
23 : namespace http {
24 :
25 : /** A sink for streaming JSON data to a parser.
26 :
27 : This class wraps a `boost::json::stream_parser` and satisfies the
28 : @ref capy::WriteSink concept, enabling incremental JSON parsing
29 : from any data source that produces buffer sequences.
30 :
31 : Since JSON parsing is synchronous, all operations return
32 : @ref capy::immediate awaitables with zero suspension overhead.
33 :
34 : @par Example
35 : @code
36 : json_sink sink;
37 :
38 : // Write JSON data incrementally
39 : auto [ec1, n1] = co_await sink.write(capy::make_buffer("{\"key\":"));
40 : auto [ec2, n2] = co_await sink.write(capy::make_buffer("42}"), true);
41 :
42 : // Or use write_eof() separately
43 : auto [ec3] = co_await sink.write_eof();
44 :
45 : // Retrieve the parsed value
46 : json::value v = sink.release();
47 : @endcode
48 :
49 : @par Thread Safety
50 : Distinct objects: Safe.
51 : Shared objects: Unsafe.
52 :
53 : @see capy::WriteSink, json::stream_parser
54 : */
55 : class json_sink
56 : {
57 : json::stream_parser parser_;
58 :
59 : public:
60 : /** Default constructor.
61 :
62 : Constructs a sink with a default-initialized stream parser.
63 : */
64 8 : json_sink() = default;
65 :
66 : /** Constructor with parse options.
67 :
68 : @param opt Options controlling JSON parsing behavior.
69 : */
70 : explicit
71 1 : json_sink(json::parse_options const& opt)
72 1 : : parser_(json::storage_ptr(), opt)
73 : {
74 1 : }
75 :
76 : /** Constructor with storage and parse options.
77 :
78 : @param sp The storage to use for parsed values.
79 : @param opt Options controlling JSON parsing behavior.
80 : */
81 : json_sink(
82 : json::storage_ptr sp,
83 : json::parse_options const& opt = {})
84 : : parser_(std::move(sp), opt)
85 : {
86 : }
87 :
88 : /** Write data to the JSON parser.
89 :
90 : Writes all bytes from the buffer sequence to the stream parser.
91 :
92 : @param buffers Buffer sequence containing JSON data.
93 :
94 : @return An awaitable yielding `(error_code,std::size_t)`.
95 : On success, returns the total bytes written.
96 : */
97 : template<capy::ConstBufferSequence CB>
98 : capy::immediate<capy::io_result<std::size_t>>
99 5 : write(CB const& buffers)
100 : {
101 5 : return write(buffers, false);
102 : }
103 :
104 : /** Write data with optional end-of-stream.
105 :
106 : Writes all bytes from the buffer sequence to the stream parser.
107 : If @p eof is true, also finishes parsing.
108 :
109 : @param buffers Buffer sequence containing JSON data.
110 : @param eof If true, signals end of JSON data after writing.
111 :
112 : @return An awaitable yielding `(error_code,std::size_t)`.
113 : On success, returns the total bytes written.
114 : */
115 : template<capy::ConstBufferSequence CB>
116 : capy::immediate<capy::io_result<std::size_t>>
117 12 : write(CB const& buffers, bool eof)
118 : {
119 12 : system::error_code ec;
120 12 : std::size_t total = 0;
121 12 : auto const end = capy::end(buffers);
122 23 : for(auto it = capy::begin(buffers); it != end; ++it)
123 : {
124 12 : capy::const_buffer buf(*it);
125 24 : auto n = parser_.write(
126 12 : static_cast<char const*>(buf.data()),
127 : buf.size(),
128 : ec);
129 12 : total += n;
130 12 : if(ec.failed())
131 1 : return {ec, total};
132 : }
133 :
134 11 : if(eof)
135 : {
136 6 : parser_.finish(ec);
137 6 : if(ec.failed())
138 0 : return {ec, total};
139 : }
140 :
141 11 : return capy::ready(total);
142 : }
143 :
144 : /** Signal end of JSON data.
145 :
146 : Finishes parsing and validates the JSON is complete.
147 :
148 : @return An awaitable yielding `(error_code)`.
149 : */
150 : capy::immediate<capy::io_result<>>
151 2 : write_eof()
152 : {
153 2 : system::error_code ec;
154 2 : parser_.finish(ec);
155 2 : if(ec.failed())
156 1 : return {ec};
157 1 : return {};
158 : }
159 :
160 : /** Check if parsing is complete.
161 :
162 : @return `true` if a complete JSON value has been parsed.
163 : */
164 : bool
165 11 : done() const noexcept
166 : {
167 11 : return parser_.done();
168 : }
169 :
170 : /** Release the parsed JSON value.
171 :
172 : Returns the parsed value and resets the parser for reuse.
173 :
174 : @par Preconditions
175 : `this->done() == true`
176 :
177 : @return The parsed JSON value.
178 : */
179 : json::value
180 7 : release()
181 : {
182 7 : return parser_.release();
183 : }
184 :
185 : /** Reset the parser for a new JSON value.
186 :
187 : Clears all state and prepares to parse a new value.
188 : */
189 : void
190 1 : reset()
191 : {
192 1 : parser_.reset();
193 1 : }
194 : };
195 :
196 : } // namespace http
197 : } // namespace boost
198 :
199 : #endif
|