Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions include/boost/buffers/read_source.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,87 @@ struct has_rewind<T, detail::void_t<decltype(
{
};

//-----------------------------------------------

/** Helper function for ReadSource implementations using single-buffer algorithms.

This function assists models of the ReadSource concept by handling
iteration over a mutable buffer sequence and invoking a user-provided
function for each buffer. The function integrates the results and
properly handles `error::eof`.

The provided function `f` is invoked repeatedly, once for each
buffer in the sequence, until all buffers are processed or an
error occurs. When `f` returns `error::eof` and has read some
bytes, that buffer's partial read is included in the total,
but iteration stops and `error::eof` is not propagated to the
caller. If `f` returns `error::eof` with zero bytes read on the
first call, `error::eof` is returned immediately.

@param buffers The mutable buffer sequence to read into.

@param ec Set to the error, if any occurred.

@param f A callable with signature `std::size_t(mutable_buffer, system::error_code&)`.
The function should read data into the provided buffer and return the
number of bytes read. On end-of-file, it should set `ec` to `error::eof`
and return the number of bytes read (possibly zero).

@return The total number of bytes read across all buffers.

@tparam MutableBufferSequence A type meeting the requirements
of MutableBufferSequence.

@tparam Function A callable type compatible with the signature
`std::size_t(mutable_buffer, system::error_code&)`.
*/
template<
class MutableBufferSequence,
class Function>
std::size_t
read_from_single_buffer(
MutableBufferSequence const& buffers,
system::error_code& ec,
Function f)
{
std::size_t total = 0;
auto it = buffers::begin(buffers);
auto const end_ = buffers::end(buffers);

while(it != end_)
{
mutable_buffer mb = *it;
if(mb.size() == 0)
{
++it;
continue;
}

system::error_code ec_tmp;
auto const nread = f(mb, ec_tmp);
total += nread;

if(ec_tmp.failed())
{
if(ec_tmp == error::eof && total > 0)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@claude
zero bytes and error::eof is ok, it can happen with empty files and empty content bodies

Copy link

@claude claude bot Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude finished @vinniefalco's task —— View job


Acknowledgment

Understood! The current implementation correctly handles both cases:

  1. EOF with total > 0: Returns success with the accumulated byte count (line 160-165)
  2. EOF with total == 0: Propagates the EOF error (line 166-168)

This allows empty files and empty content bodies to properly signal EOF on the first read with zero bytes, which is the expected behavior for these cases.


{
// EOF after reading some bytes - return success
ec = {};
return total;
}
// Propagate the error
ec = ec_tmp;
return total;
}

// Successfully read from this buffer
++it;
}

ec = {};
return total;
}

} // buffers
} // boost

Expand Down
119 changes: 119 additions & 0 deletions test/unit/read_source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

#include <boost/core/detail/static_assert.hpp>
#include <boost/core/detail/string_view.hpp>
#include <boost/core/span.hpp>

#include <cstring>

#include "test_suite.hpp"

Expand Down Expand Up @@ -139,8 +142,124 @@ BOOST_CORE_STATIC_ASSERT( has_rewind<test_source4>::value);

struct read_source_test
{
void
testReadFromSingleBuffer()
{
// Test basic functionality
{
core::string_view s = "Hello, World!";
std::size_t pos = 0;

auto read_fn = [&](mutable_buffer mb, system::error_code& ec) -> std::size_t
{
if(pos >= s.size())
{
ec = error::eof;
return 0;
}
auto const n = (std::min)(mb.size(), s.size() - pos);
std::memcpy(mb.data(), s.data() + pos, n);
pos += n;
if(pos >= s.size())
ec = error::eof;
return n;
};

char buf[20];
mutable_buffer mb(buf, sizeof(buf));
system::error_code ec;
auto const nread = read_from_single_buffer(mb, ec, read_fn);

BOOST_TEST(nread == s.size());
BOOST_TEST(! ec.failed());
BOOST_TEST(core::string_view(buf, nread) == s);
}

// Test EOF on first call
{
auto read_fn = [](mutable_buffer, system::error_code& ec) -> std::size_t
{
ec = error::eof;
return 0;
};

char buf[10];
mutable_buffer mb(buf, sizeof(buf));
system::error_code ec;
auto const nread = read_from_single_buffer(mb, ec, read_fn);

BOOST_TEST(nread == 0);
BOOST_TEST(ec == error::eof);
}

// Test EOF after partial read
{
core::string_view s = "Hi";
std::size_t call_count = 0;

auto read_fn = [&](mutable_buffer mb, system::error_code& ec) -> std::size_t
{
++call_count;
if(call_count == 1)
{
// First call: read 2 bytes
auto const n = (std::min)(mb.size(), s.size());
std::memcpy(mb.data(), s.data(), n);
return n;
}
// Second call: EOF
ec = error::eof;
return 0;
};

char buf1[5];
char buf2[5];
mutable_buffer bufs[2] = { mutable_buffer(buf1, 5), mutable_buffer(buf2, 5) };
system::error_code ec;
auto const nread = read_from_single_buffer(
span<mutable_buffer const>(bufs, 2), ec, read_fn);

BOOST_TEST(nread == 2);
BOOST_TEST(! ec.failed());
}

// Test empty buffers
{
auto read_fn = [](mutable_buffer mb, system::error_code&) -> std::size_t
{
return mb.size();
};

mutable_buffer empty_buf(nullptr, 0);
system::error_code ec;
auto const nread = read_from_single_buffer(empty_buf, ec, read_fn);

BOOST_TEST(nread == 0);
BOOST_TEST(! ec.failed());
}

// Test error propagation
{
auto read_fn = [](mutable_buffer, system::error_code& ec) -> std::size_t
{
ec = system::error_code(22, system::generic_category());
return 0;
};

char buf[10];
mutable_buffer mb(buf, sizeof(buf));
system::error_code ec;
auto const nread = read_from_single_buffer(mb, ec, read_fn);

BOOST_TEST(nread == 0);
BOOST_TEST(ec.failed());
BOOST_TEST(ec.value() == 22);
}
}

void run()
{
testReadFromSingleBuffer();
}
};

Expand Down
Loading