Files
url/test/unit/segments_view.cpp
Alan de Freitas 14fb347896 feat(ranges): specialize enable_borrowed_range for view types
All view types in Boost.URL have iterators that store raw pointers
to external data rather than references to the view object itself.
This means iterators remain valid even after the view is destroyed,
satisfying the borrowed_range requirements.

Specializations added for:
- decode_view, pct_string_view
- params_view, params_ref, params_encoded_view, params_encoded_ref
- segments_view, segments_ref, segments_encoded_view, segments_encoded_ref

fix #927
2026-01-26 12:01:22 -05:00

424 lines
12 KiB
C++

//
// Copyright (c) 2019 Vinnie Falco (vinnie.falco@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
//
// Test that header file is self-contained.
#include <boost/url/segments_view.hpp>
#include <boost/url/parse.hpp>
#include <boost/url/parse_path.hpp>
#include <boost/url/url.hpp>
#include <boost/core/detail/static_assert.hpp>
#include <boost/core/ignore_unused.hpp>
#include "test_suite.hpp"
#include <sstream>
#include <string>
#ifdef assert
#undef assert
#endif
#define assert BOOST_TEST
namespace boost {
namespace urls {
BOOST_CORE_STATIC_ASSERT(
std::is_default_constructible<
segments_view>::value);
BOOST_CORE_STATIC_ASSERT(
std::is_copy_constructible<
segments_view>::value);
BOOST_CORE_STATIC_ASSERT(
std::is_copy_assignable<
segments_view>::value);
BOOST_CORE_STATIC_ASSERT(
std::is_default_constructible<
segments_view::iterator>::value);
struct segments_view_test
{
void
testSpecialMembers()
{
// segments_view()
{
segments_view ps;
BOOST_TEST(ps.empty());
BOOST_TEST(! ps.is_absolute());
BOOST_TEST_EQ(ps.buffer(), "");
BOOST_TEST_EQ(ps.size(), 0);
}
// segments_view(segments_view)
{
segments_view ps0 =
parse_path("/path/to/file.txt").value();
segments_view ps1(ps0);
BOOST_TEST_EQ(
ps0.buffer().data(),
ps1.buffer().data());
}
// segments_view(core::string_view)
{
try
{
core::string_view s = "/path/to/file.txt";
segments_view ps(s);
BOOST_TEST_PASS();
BOOST_TEST_EQ(
ps.buffer().data(), s.data());
BOOST_TEST_EQ(ps.buffer(), s);
}
catch(std::exception const&)
{
BOOST_TEST_FAIL();
}
// reserved character
BOOST_TEST_THROWS(segments_view("?"), system::system_error);
// invalid percent-escape
BOOST_TEST_THROWS(segments_view("%"), system::system_error);
BOOST_TEST_THROWS(segments_view("%F"), system::system_error);
BOOST_TEST_THROWS(segments_view("%FX"), system::system_error);
BOOST_TEST_THROWS(segments_view("%%"), system::system_error);
BOOST_TEST_THROWS(segments_view("FA%"), system::system_error);
}
// operator=(segments_view)
{
segments_view ps0("/path/to/file.txt");
segments_view ps1("/index.htm");
ps0 = ps1;
BOOST_TEST_EQ(
ps0.buffer().data(),
ps1.buffer().data());
}
// ostream
{
segments_view ps = parse_path(
"/path/to/file.txt").value();
std::stringstream ss;
ss << ps;
BOOST_TEST_EQ(ss.str(),
"/path/to/file.txt");
}
}
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 (retain separator)
{
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:
// subranges not starting at begin become absolute
{
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");
// suffix [next(begin), end) -> "/b/c"
segments_view sub4(std::next(ps.begin()), ps.end());
BOOST_TEST_EQ(sub4.size(), 2u);
BOOST_TEST(sub4.is_absolute());
BOOST_TEST_EQ(sub4.buffer(), "/b/c");
// concatenating adjoining subviews recreates original text
segments_view first_seg(ps.begin(), std::next(ps.begin()));
std::string reconstructed(
first_seg.buffer().data(),
first_seg.buffer().size());
reconstructed.append(
sub4.buffer().data(),
sub4.buffer().size());
BOOST_TEST_EQ(reconstructed, ps.buffer());
BOOST_TEST_EQ(
first_seg.buffer().decoded_size() +
sub4.buffer().decoded_size(),
ps.buffer().decoded_size());
}
// 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()
{
// {class}
{
url_view u( "/path/to/file.txt" );
segments_view ps = u.segments();
assert( ps.buffer().data() == u.buffer().data() );
ignore_unused(ps);
}
}
void
testBorrowedRange()
{
#ifdef BOOST_URL_HAS_CONCEPTS
// segments_view is a borrowed range
BOOST_CORE_STATIC_ASSERT(
std::ranges::borrowed_range<segments_view>);
// iterators remain valid after the view is destroyed
segments_view::iterator it;
{
segments_view ps("/path/to/file.txt");
it = ps.begin();
}
// iterator is still valid (points to external buffer)
BOOST_TEST_EQ(*it, "path");
#endif
}
void
run()
{
testSpecialMembers();
testRangeCtor();
testJavadocs();
testBorrowedRange();
}
};
TEST_SUITE(
segments_view_test,
"boost.url.segments_view");
} // urls
} // boost