Commit 1cada153 authored by Dominik Charousset's avatar Dominik Charousset

Add URI abstraction according to RFC 3986

parent 7b832f7b
...@@ -122,6 +122,9 @@ set(LIBCAF_CORE_SRCS ...@@ -122,6 +122,9 @@ set(LIBCAF_CORE_SRCS
src/type_erased_value.cpp src/type_erased_value.cpp
src/uniform_type_info_map.cpp src/uniform_type_info_map.cpp
src/unprofiled.cpp src/unprofiled.cpp
src/uri.cpp
src/uri_builder.cpp
src/uri_impl.cpp
src/work_sharing.cpp src/work_sharing.cpp
src/work_stealing.cpp src/work_stealing.cpp
) )
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include "caf/config.hpp"
#include "caf/detail/parser/add_ascii.hpp"
#include "caf/detail/parser/chars.hpp"
#include "caf/detail/parser/read_ipv6_address.hpp"
#include "caf/detail/parser/state.hpp"
#include "caf/detail/scope_guard.hpp"
#include "caf/pec.hpp"
#include "caf/uri.hpp"
CAF_PUSH_UNUSED_LABEL_WARNING
#include "caf/detail/parser/fsm.hpp"
namespace caf {
namespace detail {
namespace parser {
// foo://example.com:8042/over/there?name=ferret#nose
// \_/ \______________/\_________/ \_________/ \__/
// | | | | |
// scheme authority path query fragment
// | _____________________|__
// / \ / \.
// urn:example:animal:ferret:nose
// Unlike our other parsers, the URI parsers only check for validity and
// generate ranges for the subcomponents. URIs can't have linebreaks, so we can
// safely keep track of the position by looking at the column.
template <class Iterator, class Sentinel>
void read_uri_percent_encoded(state<Iterator, Sentinel>& ps, std::string& str) {
uint8_t char_code = 0;
auto g = make_scope_guard([&] {
if (ps.code <= pec::trailing_character)
str += static_cast<char>(char_code);
});
start();
state(init) {
transition(read_nibble, hexadecimal_chars, add_ascii<16>(char_code, ch))
}
state(read_nibble) {
transition(done, hexadecimal_chars, add_ascii<16>(char_code, ch))
}
term_state(done) {
// nop
}
fin();
}
template <class Iterator, class Sentinel, class Consumer>
void read_uri_query(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
// Local variables.
uri::query_map result;
std::string key;
std::string value;
// Utility functions.
auto take_str = [&](std::string& str) {
using std::swap;
std::string res;
swap(str, res);
return std::move(res);
};
auto push = [&] {
result.emplace(take_str(key), take_str(value));
};
// Call consumer on exit.
auto g = make_scope_guard([&] {
if (ps.code <= pec::trailing_character)
consumer.query(std::move(result));
});
// FSM declaration.
start();
// query may be empty
term_state(init) {
transition(read_key, alphabetic_chars, key += ch)
}
state(read_key) {
transition(read_key, alphanumeric_chars, key += ch)
transition(read_value, '=')
}
term_state(read_value, push()) {
transition(read_value, alphanumeric_chars, value += ch)
transition(read_key, '&', push())
}
fin();
}
template <class Iterator, class Sentinel, class Consumer>
void read_uri(state<Iterator, Sentinel>& ps, Consumer&& consumer) {
// Local variables.
std::string str;
uint16_t port = 0;
// Replaces `str` with a default constructed string to make sure we're never
// operating on a moved-from string object.
auto take_str = [&] {
using std::swap;
std::string res;
swap(str, res);
return std::move(res);
};
// Allowed character sets.
auto path_char = [](char c) {
return in_whitelist(alphanumeric_chars, c) || c == '/';
};
auto authority_char = [](char c) {
return in_whitelist(alphanumeric_chars, c) || c == '.';
};
// Utility setters for avoiding code duplication.
auto set_path = [&] {
consumer.path(take_str());
};
auto set_host = [&] {
consumer.host(take_str());
};
auto set_userinfo = [&] {
consumer.userinfo(take_str());
};
// Consumer for reading IPv6 addresses.
struct {
Consumer& f;
void value(ipv6_address addr) {
f.host(addr);
}
} ip_consumer{consumer};
// FSM declaration.
start();
state(init) {
epsilon(read_scheme)
}
state(read_scheme) {
fsm_transition(read_uri_percent_encoded(ps, str), read_scheme, '%')
transition(read_scheme, alphabetic_chars, str += ch)
transition(have_scheme, ':', consumer.scheme(take_str()))
}
state(have_scheme) {
transition(disambiguate_path, '/')
transition(read_path, alphanumeric_chars, str += ch)
fsm_transition(read_uri_percent_encoded(ps, str), read_path, '%')
}
// This state is terminal, because "file:/" is a valid URI.
term_state(disambiguate_path, consumer.path("/")) {
transition(start_authority, '/')
epsilon(read_path, any_char, str += '/')
}
state(start_authority) {
transition(read_authority, authority_char, str += ch)
fsm_transition(read_ipv6_address(ps, ip_consumer), end_of_ipv6_host, '[')
}
state(end_of_ipv6_host) {
transition(end_of_host, ']')
}
term_state(end_of_host) {
transition(start_port, ':', set_host())
epsilon(end_of_authority, "/?#", set_host())
}
term_state(read_authority, set_host()) {
transition(read_authority, authority_char, str += ch)
transition(start_host, '@', set_userinfo())
transition(start_port, ':', set_host())
epsilon(end_of_authority, "/?#", set_host())
}
state(start_host) {
transition(read_host, authority_char, str += ch)
fsm_transition(read_ipv6_address(ps, ip_consumer), end_of_ipv6_host, '[')
}
term_state(read_host, set_host()) {
transition(read_host, authority_char, str += ch)
transition(start_port, ':', set_host())
epsilon(end_of_authority, "/?#", set_host())
}
state(start_port) {
transition(read_port, decimal_chars, add_ascii<10>(port, ch))
}
term_state(read_port, consumer.port(port)) {
transition(read_port, decimal_chars, add_ascii<10>(port, ch),
pec::integer_overflow)
epsilon(end_of_authority, "/?#", set_host())
}
term_state(end_of_authority) {
transition(read_path, '/')
transition(start_query, '?')
transition(read_fragment, '#')
}
term_state(read_path, set_path()) {
transition(read_path, path_char, str += ch)
transition(start_query, '?', set_path())
transition(read_fragment, '#', set_path())
}
term_state(start_query) {
fsm_epsilon(read_uri_query(ps, consumer), end_of_query)
}
unstable_state(end_of_query) {
transition(read_fragment, '#')
epsilon(done)
}
term_state(read_fragment, consumer.fragment(take_str())) {
transition(read_fragment, alphanumeric_chars, str += ch)
}
term_state(done) {
// nop
}
fin();
}
} // namespace parser
} // namespace detail
} // namespace caf
#include "caf/detail/parser/fsm_undef.hpp"
CAF_POP_WARNINGS
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <atomic>
#include <cstdint>
#include <string>
#include <utility>
#include "caf/fwd.hpp"
#include "caf/uri.hpp"
#include "caf/ref_counted.hpp"
#include "caf/string_view.hpp"
namespace caf {
namespace detail {
class uri_impl {
public:
// -- constructors, destructors, and assignment operators --------------------
uri_impl();
uri_impl(const uri_impl&) = delete;
uri_impl& operator=(const uri_impl&) = delete;
// -- member variables -------------------------------------------------------
static uri_impl default_instance;
/// Null-terminated buffer for holding the string-representation of the URI.
std::string str;
/// Scheme component.
std::string scheme;
/// Assembled authority component.
uri::authority_type authority;
/// Path component.
std::string path;
/// Query component as key-value pairs.
uri::query_map query;
/// The fragment component.
std::string fragment;
// -- modifiers --------------------------------------------------------------
/// Assembles the human-readable string representation for this URI.
void assemble_str();
// -- friend functions -------------------------------------------------------
friend void intrusive_ptr_add_ref(const uri_impl* p);
friend void intrusive_ptr_release(const uri_impl* p);
private:
// -- member variables -------------------------------------------------------
mutable std::atomic<size_t> rc_;
};
} // namespace detail
} // namespace caf
...@@ -128,6 +128,8 @@ class string_view; ...@@ -128,6 +128,8 @@ class string_view;
class type_erased_tuple; class type_erased_tuple;
class type_erased_value; class type_erased_value;
class uniform_type_info_map; class uniform_type_info_map;
class uri;
class uri_builder;
// -- structs ------------------------------------------------------------------ // -- structs ------------------------------------------------------------------
...@@ -239,10 +241,15 @@ template <class> class type_erased_value_impl; ...@@ -239,10 +241,15 @@ template <class> class type_erased_value_impl;
template <class> class stream_distribution_tree; template <class> class stream_distribution_tree;
class disposer; class disposer;
class message_data; class dynamic_message_data;
class group_manager; class group_manager;
class message_data;
class private_thread; class private_thread;
class dynamic_message_data; class uri_impl;
void intrusive_ptr_add_ref(const uri_impl* p);
void intrusive_ptr_release(const uri_impl* p);
} // namespace detail } // namespace detail
......
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <cstdint>
#include <vector>
#include "caf/detail/comparable.hpp"
#include "caf/detail/unordered_flat_map.hpp"
#include "caf/fwd.hpp"
#include "caf/intrusive_ptr.hpp"
#include "caf/ip_address.hpp"
#include "caf/string_view.hpp"
#include "caf/variant.hpp"
namespace caf {
/// A URI according to RFC 3986.
class uri : detail::comparable<uri>, detail::comparable<uri, string_view> {
public:
// -- member types -----------------------------------------------------------
/// Pointer to implementation.
using impl_ptr = intrusive_ptr<const detail::uri_impl>;
/// Host subcomponent of the authority component. Either an IP address or
/// an hostname as string.
using host_type = variant<std::string, ip_address>;
/// Bundles the authority component of the URI, i.e., userinfo, host, and
/// port.
struct authority_type {
std::string userinfo;
host_type host;
uint16_t port;
inline authority_type() : port(0) {
// nop
}
};
/// Separates the query component into key-value pairs.
using path_list = std::vector<string_view>;
/// Separates the query component into key-value pairs.
using query_map = detail::unordered_flat_map<std::string, std::string>;
// -- constructors, destructors, and assignment operators --------------------
uri();
uri(uri&&) = default;
uri(const uri&) = default;
uri& operator=(uri&&) = default;
uri& operator=(const uri&) = default;
explicit uri(impl_ptr ptr);
// -- properties -------------------------------------------------------------
/// Returns whether all components of this URI are empty.
bool empty() const noexcept;
/// Returns the full URI as provided by the user.
string_view str() const noexcept;
/// Returns the scheme component.
string_view scheme() const noexcept;
/// Returns the authority component.
const authority_type& authority() const noexcept;
/// Returns the path component as provided by the user.
string_view path() const noexcept;
/// Returns the query component as key-value map.
const query_map& query() const noexcept;
/// Returns the fragment component.
string_view fragment() const noexcept;
// -- comparison -------------------------------------------------------------
int compare(const uri& other) const noexcept;
int compare(string_view x) const noexcept;
private:
impl_ptr impl_;
};
// -- related free functions ---------------------------------------------------
/// @relates uri
std::string to_string(const uri::host_type& x);
/// @relates uri
std::string to_string(const uri::authority_type& x);
/// @relates uri
std::string to_string(const uri& x);
/// @relates uri
error parse(string_view str, uri& dest);
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#pragma once
#include <cstdint>
#include <string>
#include "caf/fwd.hpp"
#include "caf/uri.hpp"
namespace caf {
class uri_builder {
public:
// -- member types -----------------------------------------------------------
/// Pointer to implementation.
using impl_ptr = intrusive_ptr<detail::uri_impl>;
// -- constructors, destructors, and assignment operators --------------------
uri_builder();
uri_builder(uri_builder&&) = default;
uri_builder& operator=(uri_builder&&) = default;
// -- setter -----------------------------------------------------------------
uri_builder& scheme(std::string str);
uri_builder& userinfo(std::string str);
uri_builder& host(std::string str);
uri_builder& host(ip_address addr);
uri_builder& port(uint16_t value);
uri_builder& path(std::string str);
uri_builder& query(uri::query_map map);
uri_builder& fragment(std::string str);
// -- factory functions ------------------------------------------------------
uri make();
private:
// -- member variables -------------------------------------------------------
impl_ptr impl_;
};
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/uri.hpp"
#include "caf/detail/parser/read_uri.hpp"
#include "caf/detail/uri_impl.hpp"
#include "caf/error.hpp"
#include "caf/make_counted.hpp"
#include "caf/uri_builder.hpp"
namespace caf {
uri::uri() : impl_(&detail::uri_impl::default_instance) {
// nop
}
uri::uri(impl_ptr ptr) : impl_(std::move(ptr)) {
CAF_ASSERT(impl_ != nullptr);
}
bool uri::empty() const noexcept {
return str().empty();
}
string_view uri::str() const noexcept {
return impl_->str;
}
string_view uri::scheme() const noexcept {
return impl_->scheme;
}
const uri::authority_type& uri::authority() const noexcept {
return impl_->authority;
}
string_view uri::path() const noexcept {
return impl_->path;
}
const uri::query_map& uri::query() const noexcept {
return impl_->query;
}
string_view uri::fragment() const noexcept {
return impl_->fragment;
}
// -- comparison ---------------------------------------------------------------
int uri::compare(const uri& other) const noexcept {
return str().compare(other.str());
}
int uri::compare(string_view x) const noexcept {
return string_view{str()}.compare(x);
}
// -- related free functions ---------------------------------------------------
std::string to_string(const uri::host_type& x) {
auto str = get_if<std::string>(&x);
if (str)
return *str;
auto addr = get<ip_address>(x);
if (addr.embeds_v4())
return to_string(addr.embedded_v4());
std::string result = to_string(addr);
result.insert(result.begin(), '[');
result += ']';
return result;
}
std::string to_string(const uri::authority_type& x) {
std::string result;
if (!x.userinfo.empty()) {
result += x.userinfo;
result += '@';
}
result += to_string(x.host);
if (x.port != 0) {
result += ':';
result += std::to_string(x.port);
}
return result;
}
std::string to_string(const uri& x) {
auto x_str = x.str();
std::string result{x_str.begin(), x_str.end()};
return result;
}
error parse(string_view str, uri& dest) {
using namespace detail::parser;
uri_builder builder;
state<string_view::iterator> res{str.begin(), str.end()};
read_uri(res, builder);
if (res.code == pec::success) {
dest = builder.make();
return none;
}
return make_error(res.code, res.line, res.column);
}
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/uri_builder.hpp"
#include "caf/detail/uri_impl.hpp"
#include "caf/make_counted.hpp"
namespace caf {
uri_builder::uri_builder() : impl_(make_counted<detail::uri_impl>()) {
// nop
}
uri_builder& uri_builder::scheme(std::string str) {
impl_->scheme = std::move(str);
return *this;
}
uri_builder& uri_builder::userinfo(std::string str) {
impl_->authority.userinfo = std::move(str);
return *this;
}
uri_builder& uri_builder::host(std::string str) {
impl_->authority.host = std::move(str);
return *this;
}
uri_builder& uri_builder::host(ip_address addr) {
impl_->authority.host = addr;
return *this;
}
uri_builder& uri_builder::port(uint16_t value) {
impl_->authority.port = value;
return *this;
}
uri_builder& uri_builder::path(std::string str) {
impl_->path = std::move(str);
return *this;
}
uri_builder& uri_builder::query(uri::query_map map) {
impl_->query = std::move(map);
return *this;
}
uri_builder& uri_builder::fragment(std::string str) {
impl_->fragment = std::move(str);
return *this;
}
uri uri_builder::make() {
impl_->assemble_str();
return uri{std::move(impl_)};
}
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/detail/uri_impl.hpp"
#include "caf/detail/parser/read_uri.hpp"
#include "caf/error.hpp"
#include "caf/ip_address.hpp"
#include "caf/string_algorithms.hpp"
namespace caf {
namespace detail {
// -- constructors, destructors, and assignment operators ----------------------
uri_impl::uri_impl() : rc_(1) {
// nop
}
// -- member variables ---------------------------------------------------------
uri_impl uri_impl::default_instance;
// -- modifiers ----------------------------------------------------------------
void uri_impl::assemble_str() {
// TODO: all substrings need percent-escaping
str = scheme;
str += ':';
auto authority_str = to_string(authority);
if (!authority_str.empty()) {
str += "//";
str += authority_str;
if (!path.empty()) {
str += '/';
str += path;
}
} else if (!path.empty()) {
str += path;
}
if (!query.empty()) {
str += '?';
auto i = query.begin();
str += i->first;
str += '=';
str += i->second;
++i;
for (; i != query.end(); ++i) {
str += '&';
str += i->first;
str += '=';
str += i->second;
}
}
if (!fragment.empty()) {
str += '#';
str += fragment;
}
}
// -- friend functions ---------------------------------------------------------
void intrusive_ptr_add_ref(const uri_impl* p) {
p->rc_.fetch_add(1, std::memory_order_relaxed);
}
void intrusive_ptr_release(const uri_impl* p) {
if (p->rc_ == 1 || p->rc_.fetch_sub(1, std::memory_order_acq_rel) == 1)
delete p;
}
} // namespace detail
} // namespace caf
/******************************************************************************
* ____ _ _____ *
* / ___| / \ | ___| C++ *
* | | / _ \ | |_ Actor *
* | |___ / ___ \| _| Framework *
* \____/_/ \_|_| *
* *
* Copyright 2011-2018 Dominik Charousset *
* *
* Distributed under the terms and conditions of the BSD 3-Clause License or *
* (at your option) under the terms and conditions of the Boost Software *
* License 1.0. See accompanying files LICENSE and LICENSE_ALTERNATIVE. *
* *
* If you did not receive a copy of the license files, see *
* http://opensource.org/licenses/BSD-3-Clause and *
* http://www.boost.org/LICENSE_1_0.txt. *
******************************************************************************/
#include "caf/config.hpp"
#define CAF_SUITE uri
#include "caf/test/dsl.hpp"
#include "caf/ipv4_address.hpp"
#include "caf/uri.hpp"
#include "caf/uri_builder.hpp"
using namespace caf;
namespace {
struct authority_separator_t {} authority_separator;
struct path_separator_t {} path_separator;
struct uri_str_builder {
std::string res;
uri_str_builder() : res("http:") {
// nop
}
uri_str_builder& add() {
return *this;
}
template <class T, class... Ts>
uri_str_builder& add(const T& x, const Ts&... xs) {
res += x;
return add(xs...);
}
template <class... Ts>
uri_str_builder& add(const authority_separator_t&, const Ts&... xs) {
if (res.back() == ':')
return add("//", xs...);
return add(xs...);
}
template <class... Ts>
uri_str_builder& add(const path_separator_t&, const Ts&... xs) {
if (res.back() != ':')
return add("/", xs...);
return add(xs...);
}
uri_str_builder& userinfo(std::string str) {
return add(authority_separator, str, '@');
}
uri_str_builder& host(std::string str) {
return add(authority_separator, str);
}
uri_str_builder& host(ip_address addr) {
return add(authority_separator, '[', to_string(addr), ']');
}
uri_str_builder& port(uint16_t value) {
return add(':', std::to_string(value));
}
uri_str_builder& path(std::string str) {
return add(path_separator, str);
}
uri_str_builder& query(uri::query_map map) {
if (map.empty())
return *this;
res += '?';
auto i = map.begin();
res += i->first;
res += '=';
res += i->second;
for (; i != map.end(); ++i) {
res += '&';
res += i->first;
res += '=';
res += i->second;
}
return *this;
}
uri_str_builder& fragment(std::string str) {
return add('#', str);
}
std::string operator*() {
using std::swap;
std::string str = "http:";
swap(str, res);
return str;
}
};
struct fixture {
uri_builder http;
uri_str_builder http_str;
fixture() {
http.scheme("http");
}
};
struct me_t {} me;
template <class T>
T& operator<<(T& builder, me_t) {
return builder.userinfo("me");
}
struct node_t {} node;
template <class T>
T& operator<<(T& builder, node_t) {
return builder.host("node");
}
struct port80_t {} port80;
template <class T>
T& operator<<(T& builder, port80_t) {
return builder.port(80);
}
struct file_t {} file;
template <class T>
T& operator<<(T& builder, file_t) {
return builder.path("file");
}
struct frag_t {} frag;
template <class T>
T& operator<<(T& builder, frag_t) {
return builder.fragment("42");
}
struct kvp_t {} kvp;
template <class T>
T& operator<<(T& builder, kvp_t) {
return builder.query(uri::query_map{{"a", "1"}, {"b", "2"}});
}
uri operator*(uri_builder& builder) {
auto result = builder.make();
builder = uri_builder();
auto scheme = result.scheme();
builder.scheme(std::string{scheme.begin(), scheme.end()});
return result;
}
uri operator "" _u(const char* cstr, size_t cstr_len) {
uri result;
string_view str{cstr, cstr_len};
auto err = parse(str, result);
if (err)
CAF_FAIL("error while parsing " << str << ": " << to_string(err));
return result;
}
bool operator "" _i(const char* cstr, size_t cstr_len) {
uri result;
string_view str{cstr, cstr_len};
auto err = parse(str, result);
return err != none;
}
} // namespace <anonymous>
CAF_TEST_FIXTURE_SCOPE(uri_tests, fixture)
CAF_TEST(constructing) {
uri x;
CAF_CHECK_EQUAL(x.empty(), true);
CAF_CHECK_EQUAL(x.str(), "");
}
#define BUILD(components) \
CAF_CHECK_EQUAL(*(http << components), *(http_str << components))
CAF_TEST(builder construction) {
BUILD(file);
BUILD(file << kvp);
BUILD(file << frag);
BUILD(file << kvp << frag);
BUILD(node);
BUILD(node << frag);
BUILD(node << kvp);
BUILD(node << kvp << frag);
BUILD(node << port80);
BUILD(node << port80 << frag);
BUILD(node << port80 << kvp);
BUILD(node << port80 << kvp << frag);
BUILD(me << node);
BUILD(me << node << kvp);
BUILD(me << node << frag);
BUILD(me << node << kvp << frag);
BUILD(me << node << port80);
BUILD(me << node << port80 << frag);
BUILD(me << node << port80 << kvp);
BUILD(me << node << port80 << kvp << frag);
BUILD(node << file);
BUILD(node << file << frag);
BUILD(node << file << kvp);
BUILD(node << file << kvp << frag);
BUILD(node << port80 << file);
BUILD(node << port80 << file << frag);
BUILD(node << port80 << file << kvp);
BUILD(node << port80 << file << kvp << frag);
BUILD(me << node << file);
BUILD(me << node << file << frag);
BUILD(me << node << file << kvp);
BUILD(me << node << file << kvp << frag);
BUILD(me << node << port80 << file);
BUILD(me << node << port80 << file << frag);
BUILD(me << node << port80 << file << kvp);
BUILD(me << node << port80 << file << kvp << frag);
}
#define ROUNDTRIP(str) CAF_CHECK_EQUAL(str##_u, str)
CAF_TEST(from string) {
ROUNDTRIP("http:file");
ROUNDTRIP("http:file?a=1&b=2");
ROUNDTRIP("http:file#42");
ROUNDTRIP("http:file?a=1&b=2#42");
ROUNDTRIP("http://node");
ROUNDTRIP("http://node?a=1&b=2");
ROUNDTRIP("http://node#42");
ROUNDTRIP("http://node?a=1&b=2#42");
ROUNDTRIP("http://node:80");
ROUNDTRIP("http://node:80?a=1&b=2");
ROUNDTRIP("http://node:80#42");
ROUNDTRIP("http://node:80?a=1&b=2#42");
ROUNDTRIP("http://me@node");
ROUNDTRIP("http://me@node?a=1&b=2");
ROUNDTRIP("http://me@node#42");
ROUNDTRIP("http://me@node?a=1&b=2#42");
ROUNDTRIP("http://me@node:80");
ROUNDTRIP("http://me@node:80?a=1&b=2");
ROUNDTRIP("http://me@node:80#42");
ROUNDTRIP("http://me@node:80?a=1&b=2#42");
ROUNDTRIP("http://node/file");
ROUNDTRIP("http://node/file?a=1&b=2");
ROUNDTRIP("http://node/file#42");
ROUNDTRIP("http://node/file?a=1&b=2#42");
ROUNDTRIP("http://node:80/file");
ROUNDTRIP("http://node:80/file?a=1&b=2");
ROUNDTRIP("http://node:80/file#42");
ROUNDTRIP("http://node:80/file?a=1&b=2#42");
ROUNDTRIP("http://me@node/file");
ROUNDTRIP("http://me@node/file?a=1&b=2");
ROUNDTRIP("http://me@node/file#42");
ROUNDTRIP("http://me@node/file?a=1&b=2#42");
ROUNDTRIP("http://me@node:80/file");
ROUNDTRIP("http://me@node:80/file?a=1&b=2");
ROUNDTRIP("http://me@node:80/file#42");
ROUNDTRIP("http://me@node:80/file?a=1&b=2#42");
ROUNDTRIP("http://[::1]");
ROUNDTRIP("http://[::1]?a=1&b=2");
ROUNDTRIP("http://[::1]#42");
ROUNDTRIP("http://[::1]?a=1&b=2#42");
ROUNDTRIP("http://[::1]:80");
ROUNDTRIP("http://[::1]:80?a=1&b=2");
ROUNDTRIP("http://[::1]:80#42");
ROUNDTRIP("http://[::1]:80?a=1&b=2#42");
ROUNDTRIP("http://me@[::1]");
ROUNDTRIP("http://me@[::1]?a=1&b=2");
ROUNDTRIP("http://me@[::1]#42");
ROUNDTRIP("http://me@[::1]?a=1&b=2#42");
ROUNDTRIP("http://me@[::1]:80");
ROUNDTRIP("http://me@[::1]:80?a=1&b=2");
ROUNDTRIP("http://me@[::1]:80#42");
ROUNDTRIP("http://me@[::1]:80?a=1&b=2#42");
ROUNDTRIP("http://[::1]/file");
ROUNDTRIP("http://[::1]/file?a=1&b=2");
ROUNDTRIP("http://[::1]/file#42");
ROUNDTRIP("http://[::1]/file?a=1&b=2#42");
ROUNDTRIP("http://[::1]:80/file");
ROUNDTRIP("http://[::1]:80/file?a=1&b=2");
ROUNDTRIP("http://[::1]:80/file#42");
ROUNDTRIP("http://[::1]:80/file?a=1&b=2#42");
ROUNDTRIP("http://me@[::1]/file");
ROUNDTRIP("http://me@[::1]/file?a=1&b=2");
ROUNDTRIP("http://me@[::1]/file#42");
ROUNDTRIP("http://me@[::1]/file?a=1&b=2#42");
ROUNDTRIP("http://me@[::1]:80/file");
ROUNDTRIP("http://me@[::1]:80/file?a=1&b=2");
ROUNDTRIP("http://me@[::1]:80/file#42");
ROUNDTRIP("http://me@[::1]:80/file?a=1&b=2#42");
}
CAF_TEST(empty components) {
CAF_CHECK_EQUAL("foo:/"_u, "foo:/");
CAF_CHECK_EQUAL("foo:/#"_u, "foo:/");
CAF_CHECK_EQUAL("foo:/?"_u, "foo:/");
CAF_CHECK_EQUAL("foo:/?#"_u, "foo:/");
CAF_CHECK_EQUAL("foo:bar#"_u, "foo:bar");
CAF_CHECK_EQUAL("foo:bar?"_u, "foo:bar");
CAF_CHECK_EQUAL("foo:bar?#"_u, "foo:bar");
CAF_CHECK_EQUAL("foo://bar#"_u, "foo://bar");
CAF_CHECK_EQUAL("foo://bar?"_u, "foo://bar");
CAF_CHECK_EQUAL("foo://bar?#"_u, "foo://bar");
}
CAF_TEST(invalid uris) {
CAF_CHECK("http"_i);
CAF_CHECK("http://"_i);
CAF_CHECK("http://foo:66000"_i);
}
CAF_TEST_FIXTURE_SCOPE_END()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment