2
0
mirror of https://github.com/boostorg/url.git synced 2026-01-19 04:42:15 +00:00

feat: segments_view iterator constructors

This commit is contained in:
Alan de Freitas
2025-10-21 19:06:35 -05:00
parent f27306e613
commit 0b7fd495a4
9 changed files with 531 additions and 0 deletions

View File

@@ -0,0 +1,163 @@
//
// Copyright (c) 2025 Alan de Freitas (alandefreitas@gmail.com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/url
//
#ifndef BOOST_URL_DETAIL_SEGMENTS_RANGE_HPP
#define BOOST_URL_DETAIL_SEGMENTS_RANGE_HPP
#include <boost/url/detail/config.hpp>
#include <boost/url/detail/url_impl.hpp>
#include <boost/url/segments_base.hpp>
#include <boost/url/segments_encoded_base.hpp>
#include <boost/core/detail/string_view.hpp>
#include <boost/assert.hpp>
namespace boost {
namespace urls {
namespace detail {
struct segments_iter_access
{
static
segments_iter_impl const&
impl(segments_base::iterator const& it) noexcept
{
return it.it_;
}
static
segments_iter_impl const&
impl(segments_encoded_base::iterator const& it) noexcept
{
return it.it_;
}
};
inline
path_ref
make_subref_from_impls(
segments_iter_impl const& first,
segments_iter_impl const& last) noexcept
{
BOOST_ASSERT(first.ref.alias_of(last.ref));
path_ref const& ref = first.ref;
std::size_t const i0 = first.index;
std::size_t const i1 = last.index;
BOOST_ASSERT(i0 <= i1);
std::size_t const nseg = i1 - i0;
bool const absolute = ref.buffer().starts_with('/');
// Empty range
if (nseg == 0)
{
std::size_t off0;
if (i0 == 0)
{
// [begin, begin): don't include the leading '/'
// for absolute, start right after the leading '/';
if (absolute)
{
off0 = 1;
}
// for relative, start at the first segment character.
else
{
off0 = first.pos;
}
}
else
{
// [it, it) in the middle:
// skip the separator before segment i0
off0 = first.pos + 1;
}
core::string_view const sub(ref.data() + off0, 0);
return {sub, 0, 0};
}
// General case: non-empty range
// Start offset
std::size_t off0;
bool include_leading_slash = false;
if (i0 == 0)
{
if (absolute)
{
// include leading '/'
off0 = 0;
include_leading_slash = true;
}
else
{
// relative: start at first segment
off0 = first.pos;
}
}
else
{
// skip slash before segment i0
off0 = first.pos + 1;
}
// End offset
std::size_t off1;
if(i1 == ref.nseg())
{
off1 = ref.size();
}
else
{
// stop before the slash preceding i1
off1 = last.pos;
}
BOOST_ASSERT(off1 >= off0);
core::string_view const sub(ref.data() + off0, off1 - off0);
// Decoded-length:
// sum per-segment decoded lengths + internal '/' + the leading '/'.
std::size_t dn_sum = 0;
{
// copy to iterate
segments_iter_impl cur = first;
for (std::size_t k = 0; k < nseg; ++k)
{
// per-segment decoded length
dn_sum += cur.dn;
cur.increment();
}
// internal '/'s
dn_sum += (nseg - 1);
// leading '/'
if (include_leading_slash)
{
++dn_sum;
}
}
return {sub, dn_sum, nseg};
}
template<class Iter>
inline
path_ref
make_subref(Iter const& first, Iter const& last) noexcept
{
auto const& f = segments_iter_access::impl(first);
auto const& l = segments_iter_access::impl(last);
return make_subref_from_impls(f, l);
}
} // detail
} // urls
} // boost
#endif

View File

@@ -17,6 +17,9 @@
namespace boost {
namespace urls {
namespace detail {
struct segments_iter_access;
}
class segments_base::iterator
{
@@ -24,6 +27,7 @@ class segments_base::iterator
friend class segments_base;
friend class segments_ref;
friend struct detail::segments_iter_access;
iterator(detail::path_ref const&) noexcept;
iterator(detail::path_ref const&, int) noexcept;

View File

@@ -16,6 +16,9 @@
namespace boost {
namespace urls {
namespace detail {
struct segments_iter_access;
}
class segments_encoded_base::iterator
{
@@ -24,6 +27,7 @@ class segments_encoded_base::iterator
friend class url_base;
friend class segments_encoded_base;
friend class segments_encoded_ref;
friend struct detail::segments_iter_access;
iterator(detail::path_ref const&) noexcept;
iterator(detail::path_ref const&, int) noexcept;

View File

@@ -167,6 +167,53 @@ public:
segments_encoded_view(
core::string_view s);
/** Constructor
This function creates a new @ref segments_encoded_view
from a pair of iterators referring to
elements of another encoded segments
view. The resulting view references
the same underlying character buffer
as the original.
The caller is responsible for ensuring
that the lifetime of the original buffer
extends until the constructed view
is no longer referenced.
@par Example
@code
segments_encoded_view ps( "/path/to/file.txt" );
segments_encoded_view sub(
std::next(ps.begin()),
ps.end());
// sub represents "to/file.txt"
@endcode
@par Preconditions
The iterators must be valid and belong to
the same @ref segments_encoded_view.
@par Postconditions
`sub.buffer()` references characters in the
original `ps.buffer()`.
@par Complexity
Linear in `sub.buffer()`
@par Exception Safety
Throws nothing.
@param first The beginning iterator.
@param last The ending iterator.
*/
BOOST_URL_DECL
segments_encoded_view(
iterator first,
iterator last) noexcept;
/** Assignment
After assignment, both views

View File

@@ -165,6 +165,53 @@ public:
segments_view(
core::string_view s);
/** Constructor
This function creates a new @ref segments_view
from a pair of iterators referring to
elements of another segments view. The
resulting view references the same
underlying character buffer as the
original.
The caller is responsible for ensuring
that the lifetime of the original buffer
extends until the constructed view is no
longer referenced.
@par Example
@code
segments_view ps( "/path/to/file.txt" );
segments_view sub(
std::next(ps.begin()),
ps.end());
// sub represents "to/file.txt"
@endcode
@par Preconditions
The iterators must be valid and belong to
the same @ref segments_view.
@par Postconditions
`sub.buffer()` references characters in the
original `ps.buffer()`.
@par Complexity
Linear in `sub.buffer()`
@par Exception Safety
Throws nothing.
@param first The beginning iterator.
@param last The ending iterator.
*/
BOOST_URL_DECL
segments_view(
iterator first,
iterator last) noexcept;
/** Assignment
After assignment, both views

View File

@@ -10,6 +10,7 @@
#include <boost/url/detail/config.hpp>
#include <boost/url/detail/segments_range.hpp>
#include <boost/url/segments_encoded_view.hpp>
#include <boost/url/parse_path.hpp>
@@ -32,6 +33,12 @@ segments_encoded_view(
{
}
segments_encoded_view::
segments_encoded_view(iterator first, iterator last) noexcept
: segments_encoded_base(detail::make_subref(first, last))
{
}
segments_encoded_view::
operator
segments_view() const noexcept

View File

@@ -10,6 +10,7 @@
#include <boost/url/detail/config.hpp>
#include <boost/url/detail/segments_range.hpp>
#include <boost/url/segments_view.hpp>
#include <boost/url/parse_path.hpp>
@@ -32,6 +33,12 @@ segments_view(
{
}
segments_view::
segments_view(iterator first, iterator last) noexcept
: segments_base(detail::make_subref(first, last))
{
}
} // urls
} // boost

View File

@@ -95,6 +95,23 @@ struct segments_const_encoded_view_test
BOOST_TEST_THROWS(segments_encoded_view("FA%"), system::system_error);
}
// segments_encoded_view(iterator, iterator)
{
segments_encoded_view ps = parse_path("/a/b/c").value();
auto first = std::next(ps.begin());
auto last = ps.end();
segments_encoded_view sub(first, last);
BOOST_TEST_EQ(sub.size(), 2u);
BOOST_TEST(!sub.is_absolute());
BOOST_TEST_EQ(sub.buffer(), "b/c");
auto it = sub.begin();
BOOST_TEST_EQ(*it++, "b");
BOOST_TEST_EQ(*it++, "c");
BOOST_TEST(it == sub.end());
}
// operator=(segments_encoded_view)
{
segments_encoded_view ps0("/path/to/file.txt");

View File

@@ -116,6 +116,240 @@ struct segments_view_test
}
}
void
testRangeCtor()
{
// full slice equals original (absolute path)
{
segments_view ps = parse_path("/a/b/c").value();
segments_view sub(ps.begin(), ps.end());
BOOST_TEST_EQ(sub.size(), 3u);
BOOST_TEST(sub.is_absolute());
BOOST_TEST_EQ(sub.buffer(), "/a/b/c");
// alias same storage
BOOST_TEST_EQ(sub.buffer().data(), ps.buffer().data());
}
// full slice equals original (relative path)
{
segments_view ps = parse_path("a/b/c").value();
segments_view sub(ps.begin(), ps.end());
BOOST_TEST_EQ(sub.size(), 3u);
BOOST_TEST(!sub.is_absolute());
BOOST_TEST_EQ(sub.buffer(), "a/b/c");
// alias same storage
BOOST_TEST_EQ(sub.buffer().data(), ps.buffer().data());
}
// drop first segment: start at index 1 (no leading slash)
{
segments_view ps = parse_path("/a/b/c").value();
auto first = std::next(ps.begin());
auto last = ps.end();
segments_view sub(first, last);
BOOST_TEST_EQ(sub.size(), 2u);
BOOST_TEST(!sub.is_absolute());
BOOST_TEST_EQ(sub.buffer(), "b/c");
auto it = sub.begin();
BOOST_TEST_EQ(*it++, "b");
BOOST_TEST_EQ(*it++, "c");
BOOST_TEST(it == sub.end());
}
// take prefix without the last segment:
// [begin, prev(end)) -> "/a/b"
{
segments_view ps = parse_path("/a/b/c").value();
auto first = ps.begin();
auto last = std::prev(ps.end());
segments_view sub(first, last);
BOOST_TEST_EQ(sub.size(), 2u);
BOOST_TEST(sub.is_absolute());
BOOST_TEST_EQ(sub.buffer(), "/a/b");
auto it = sub.begin();
BOOST_TEST_EQ(*it++, "a");
BOOST_TEST_EQ(*it++, "b");
BOOST_TEST(it == sub.end());
}
// single segment in the middle:
// ["b", past "b") -> "b"
{
segments_view ps = parse_path("/a/b/c").value();
auto b = std::next(ps.begin());
auto e = std::next(b);
segments_view sub(b, e);
BOOST_TEST_EQ(sub.size(), 1u);
BOOST_TEST(!sub.is_absolute());
BOOST_TEST_EQ(sub.buffer(), "b");
BOOST_TEST_EQ(*sub.begin(), "b");
}
// relative path:
// no leading slash preserved in any slice
{
segments_view ps = parse_path("a/b/c").value();
// full slice
segments_view sub1(ps.begin(), ps.end());
BOOST_TEST_EQ(sub1.size(), 3u);
BOOST_TEST(!sub1.is_absolute());
BOOST_TEST_EQ(sub1.buffer(), "a/b/c");
// prefix [begin, prev(end)) -> "a/b"
segments_view sub2(ps.begin(), std::prev(ps.end()));
BOOST_TEST_EQ(sub2.size(), 2u);
BOOST_TEST(!sub2.is_absolute());
BOOST_TEST_EQ(sub2.buffer(), "a/b");
// middle one ["b", past "b") -> "b"
auto b = std::next(ps.begin());
segments_view sub3(b, std::next(b));
BOOST_TEST_EQ(sub3.size(), 1u);
BOOST_TEST(!sub3.is_absolute());
BOOST_TEST_EQ(sub3.buffer(), "b");
}
// empty subrange [it, it):
// empty buffer, not absolute
{
segments_view ps = parse_path("/a/b").value();
auto it = ps.begin();
segments_view sub(it, it);
BOOST_TEST_EQ(sub.size(), 0u);
BOOST_TEST(!sub.is_absolute());
BOOST_TEST_EQ(sub.buffer(), "");
BOOST_TEST(sub.begin() == sub.end());
}
// empty subrange from relative source
{
segments_view ps = parse_path("a/b").value();
auto it = ps.begin();
segments_view sub(it, it);
BOOST_TEST_EQ(sub.size(), 0u);
BOOST_TEST(!sub.is_absolute());
BOOST_TEST_EQ(sub.buffer(), "");
BOOST_TEST(sub.begin() == sub.end());
}
// percent-encoding:
// slice of first encoded segment only, absolute start
{
segments_view ps = parse_path("/a%2Fb/c").value();
auto first = ps.begin();
auto last = std::next(ps.begin());
segments_view sub(first, last);
// encoded in buffer
BOOST_TEST(sub.is_absolute());
BOOST_TEST_EQ(sub.size(), 1u);
BOOST_TEST_EQ(sub.buffer(), "/a%2Fb");
// decoded on deref
auto it = sub.begin();
BOOST_TEST_EQ(*it++, "a/b");
BOOST_TEST(it == sub.end());
}
// aliasing: url_view -> segments_view -> subrange
{
url_view u("/x/y");
segments_view ps = u.segments();
segments_view sub(ps.begin(), ps.end());
BOOST_TEST_EQ(sub.buffer().data(), u.buffer().data());
BOOST_TEST_EQ(sub.buffer(), "/x/y");
BOOST_TEST(sub.is_absolute());
BOOST_TEST_EQ(sub.size(), 2u);
}
// empty prefix of absolute path:
// [begin, begin) -> empty
{
segments_view ps = parse_path("/a/b/c").value();
segments_view sub(ps.begin(), ps.begin());
BOOST_TEST_EQ(sub.size(), 0u);
BOOST_TEST_EQ(sub.buffer(), "");
BOOST_TEST(!sub.is_absolute());
}
// empty subrange in the middle (absolute):
// ["b","b") -> "", not absolute
{
segments_view ps = parse_path("/a/b/c").value();
auto b = std::next(ps.begin());
segments_view sub(b, b);
BOOST_TEST_EQ(sub.size(), 0u);
BOOST_TEST_EQ(sub.buffer(), "");
BOOST_TEST(!sub.is_absolute());
BOOST_TEST(sub.begin() == sub.end());
}
// empty subrange in the middle (relative):
// ["b","b") -> "", not absolute
{
segments_view ps = parse_path("a/b/c").value();
auto b = std::next(ps.begin()); // "b"
segments_view sub(b, b);
BOOST_TEST_EQ(sub.size(), 0u);
BOOST_TEST_EQ(sub.buffer(), "");
BOOST_TEST(!sub.is_absolute());
BOOST_TEST(sub.begin() == sub.end());
}
// empty subrange at end:
// [end,end) -> "", not absolute
{
segments_view ps = parse_path("/a/b/c").value();
auto e = ps.end();
segments_view sub(e, e);
BOOST_TEST_EQ(sub.size(), 0u);
BOOST_TEST_EQ(sub.buffer(), "");
BOOST_TEST(!sub.is_absolute());
}
// single-segment absolute path full slice: "/a"
{
segments_view ps = parse_path("/a").value();
segments_view sub(ps.begin(), ps.end());
BOOST_TEST_EQ(sub.size(), 1u);
BOOST_TEST(sub.is_absolute());
BOOST_TEST_EQ(sub.buffer(), "/a");
auto it = sub.begin();
BOOST_TEST_EQ(*it++, "a");
BOOST_TEST(it == sub.end());
}
// middle slice using last.pos path:
// ["b", prev(end)) on "/a/b/c" -> "b"
{
segments_view ps = parse_path("/a/b/c").value();
auto b = std::next(ps.begin());
auto last = std::prev(ps.end());
segments_view sub(b, last);
BOOST_TEST_EQ(sub.size(), 1u);
BOOST_TEST(!sub.is_absolute());
BOOST_TEST_EQ(sub.buffer(), "b");
BOOST_TEST_EQ(*sub.begin(), "b");
}
}
void
testJavadocs()
{
@@ -135,6 +369,7 @@ struct segments_view_test
run()
{
testSpecialMembers();
testRangeCtor();
testJavadocs();
}
};