Skip to content

Commit

Permalink
Added JSON query encoding/decoding and modular changes (#65)
Browse files Browse the repository at this point in the history
* Added prototype for converting between URL components and JSON

* Added implementation and tests for converting a query string to a JSON data structure and back

* Added optional value to query_iterator

* Added error object for JSON query objects

* Moved some more deckchairs around
  • Loading branch information
glynos committed Apr 13, 2020
1 parent f2dd72f commit f4631ac
Show file tree
Hide file tree
Showing 43 changed files with 469 additions and 6,997 deletions.
1 change: 0 additions & 1 deletion .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ before_build:
-A x64
-Dskyr_WARNINGS_AS_ERRORS=OFF
-Dskyr_BUILD_TESTS=ON
-Dskyr_BUILD_WPT_TESTS=ON
-Dskyr_BUILD_DOCS=OFF
-Dskyr_BUILD_EXAMPLES=OFF
-DCMAKE_TOOLCHAIN_FILE=C:\Tools\vcpkg\scripts\buildsystems\vcpkg.cmake
Expand Down
2 changes: 1 addition & 1 deletion .clang-tidy
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Checks: '-*,bugprone-*,-bugprone-exception-escape,clang-diagnostic-*,cppcoreguidelines-*,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-macro-usage,-cppcoreguidelines-non-private-member-variables-in-classes,-cppcoreguidelines-avoid-c-arrays,misc-*,-misc-unused-parameters,-misc-non-private-member-variables-in-classes,modernize-*,-modernize-use-trailing-return-type,performance-*'
Checks: '-*,bugprone-*,-bugprone-exception-escape,clang-diagnostic-*,cppcoreguidelines-*,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-macro-usage,-cppcoreguidelines-non-private-member-variables-in-classes,-cppcoreguidelines-avoid-c-arrays,misc-*,-misc-unused-parameters,-misc-non-private-member-variables-in-classes,modernize-*,performance-*'
CheckOptions:
- key: readability-identifier-naming.ClassCase
value: lower_case
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(tl-expected CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)

This comment has been minimized.

Copy link
@oficsu

oficsu Apr 22, 2020

Is it really required when we are not building tests and examples?

This comment has been minimized.

Copy link
@glynos

glynos Apr 22, 2020

Author Owner

Not really required, no. I can add a flag that enable or disable it.

This comment has been minimized.

Copy link
@glynos

glynos Apr 22, 2020

Author Owner

Actually it is used in one of the headers, but that is optional.

cpp-netlib#75


if (${CMAKE_CXX_COMPILER_ID} MATCHES GNU OR
${CMAKE_CXX_COMPILER_ID} MATCHES Clang)
Expand Down
4 changes: 2 additions & 2 deletions examples/example_04.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
// http://www.boost.org/LICENSE_1_0.txt)

#include <iostream>
#include <skyr/core/url_parse.hpp>
#include <skyr/core/url_serialize.hpp>
#include <skyr/core/parse.hpp>
#include <skyr/core/serialize.hpp>

int main(int argc, char *argv[]) {
auto base = skyr::parse("https://example.org/");
Expand Down
56 changes: 49 additions & 7 deletions src/core/url_error.cpp → include/skyr/core/errors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,45 @@
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <string>
#include "skyr/core/url_error.hpp"
#ifndef SKYR_CORE_URL_ERROR_INC
#define SKYR_CORE_URL_ERROR_INC

#include <system_error>

namespace skyr {
inline namespace v1 {
namespace {
/// \enum url_parse_errc
/// Enumerates URL parser errors
enum class url_parse_errc {
/// The string contains an invalid Unicode character
invalid_unicode_character = 1,
/// A character is not a valid scheme character
invalid_scheme_character,
/// The URL is not an absolute URL with fragment
not_an_absolute_url_with_fragment,
/// Cannot set scheme value
cannot_override_scheme,
/// The ostname is empty
empty_hostname,
/// Invalid IPv4 address
invalid_ipv4_address,
/// Invalid IPv6 address
invalid_ipv6_address,
/// A character is a forbidden host point
forbidden_host_point,
/// Unable to decode host point
cannot_decode_host_point,
/// Invalid domain string
domain_error,
/// The URL cannot be a base URL
cannot_be_a_base_url,
/// The URL cannot have a username, password or port
cannot_have_a_username_password_or_port,
/// Invalid port value
invalid_port,
};

namespace details {
class url_parse_error_category : public std::error_category {
public:
[[nodiscard]] auto name() const noexcept -> const char * override {
Expand All @@ -32,12 +65,21 @@ class url_parse_error_category : public std::error_category {
}
}
};
} // namespace details

const url_parse_error_category category{};
} // namespace

auto make_error_code(url_parse_errc error) noexcept -> std::error_code {
/// Creates a `std::error_code` given a `skyr::url_parse_errc` value
/// \param error A URL parse error
/// \returns A `std::error_code` object
inline auto make_error_code(url_parse_errc error) noexcept -> std::error_code {
static const details::url_parse_error_category category{};
return std::error_code(static_cast<int>(error), category);
}
} // namespace v1
} // namespace skyr

namespace std {
template <>
struct is_error_code_enum<skyr::v1::url_parse_errc> : true_type {};
} // namespace std

#endif // SKYR_CORE_URL_ERROR_INC
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ inline namespace v1 {
/// \returns A `url_record` on success and an error code on failure
auto parse(
std::string_view input,
std::optional<url_record> base = std::nullopt) -> tl::expected<url_record, std::error_code>;
std::optional<url_record> base=std::nullopt) -> tl::expected<url_record, std::error_code>;
} // namespace v1
} // namespace skyr

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ inline namespace v1 {
/// serialization, if set
/// \returns A serialized URL string
auto serialize(
const url_record &url, bool exclude_fragment = false) -> url_record::string_type;
const url_record &url, bool exclude_fragment=false) -> url_record::string_type;
} // namespace v1
} // namespace skyr

Expand Down
56 changes: 0 additions & 56 deletions include/skyr/core/url_error.hpp

This file was deleted.

2 changes: 1 addition & 1 deletion include/skyr/core/url_record.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#include <string>
#include <cstdint>
#include <optional>
#include <skyr/core/url_schemes.hpp>
#include <skyr/core/schemes.hpp>

namespace skyr {
inline namespace v1 {
Expand Down
24 changes: 23 additions & 1 deletion include/skyr/domain/errors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,32 @@ enum class domain_errc {
encoding_error,
};

namespace details {
class domain_error_category : public std::error_category {
public:
[[nodiscard]] auto name() const noexcept -> const char * override {
return "domain";
}

[[nodiscard]] auto message(int error) const noexcept -> std::string override {
switch (static_cast<domain_errc>(error)) {
case domain_errc::disallowed_code_point:return "Disallowed code point";
case domain_errc::bad_input:return "Bad input";
case domain_errc::overflow:return "Overflow";
case domain_errc::encoding_error:return "Encoding error";
default:return "(Unknown error)";
}
}
};
} // namespace details

/// Creates a `std::error_code` given a `skyr::domain_errc` value
/// \param error A domain error
/// \returns A `std::error_code` object
auto make_error_code(domain_errc error) noexcept -> std::error_code;
inline auto make_error_code(domain_errc error) noexcept -> std::error_code {
static const details::domain_error_category category{};
return std::error_code(static_cast<int>(error), category);
}
} // namespace v1
} // namespace skyr

Expand Down
22 changes: 21 additions & 1 deletion include/skyr/filesystem/path.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,30 @@ enum class path_errc {
percent_decoding_error,
};

namespace details {
class path_error_category : public std::error_category {
public:
[[nodiscard]] auto name() const noexcept -> const char * override {
return "url filesystem path";
}

[[nodiscard]] auto message(int error) const noexcept -> std::string override {
switch (static_cast<path_errc>(error)) {
case path_errc::invalid_path: return "Invalid path";
case path_errc::percent_decoding_error: return "Percent decoding error";
default: return "(Unknown error)";
}
}
};
} // namespace details

/// Creates a `std::error_code` given a `skyr::path_errc` value
/// \param error A filesystem path conversion error
/// \returns A `std::error_code` object
auto make_error_code(path_errc error) noexcept -> std::error_code;
inline auto make_error_code(path_errc error) noexcept -> std::error_code {
static const details::path_error_category category{};
return std::error_code(static_cast<int>(error), category);
}

/// Converts a path object to a URL with a file protocol. Handles
/// some processing, including percent encoding
Expand Down
120 changes: 120 additions & 0 deletions include/skyr/json/json.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright 2020 Glyn Matthews.
// 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)

#ifndef SKYR_URL_JSON_HPP
#define SKYR_URL_JSON_HPP

#include <string>
#include <system_error>
#include <vector>
#include <tl/expected.hpp>
#include <nlohmann/json.hpp>
#include <skyr/query/query_iterator.hpp>
#include <skyr/percent_encoding/percent_encode.hpp>
#include <skyr/percent_encoding/percent_decode.hpp>

namespace skyr {
inline namespace v1 {
namespace json {
///
enum class json_errc {
///
invalid_query = 1,
};

namespace details {
class json_error_category : public std::error_category {
public:
[[nodiscard]] auto name() const noexcept -> const char * override {
return "url json query";
}

[[nodiscard]] auto message(int error) const noexcept -> std::string override {
switch (static_cast<json_errc>(error)) {
case json_errc::invalid_query: return "Invalid query object";
default: return "(Unknown error)";
}
}
};
} // namespace details

/// Creates a `std::error_code` given a `skyr::json::json_errc` value
/// \param error A JSON query error
/// \returns A `std::error_code` object
inline auto make_error_code(json_errc error) noexcept -> std::error_code {
static const details::json_error_category category{};
return std::error_code(static_cast<int>(error), category);
}


inline auto encode_query(const nlohmann::json &json, char separator='&', char equal='=')
-> tl::expected<std::string, std::error_code> {
using namespace std::string_literals;

auto result = ""s;

if (!json.is_object()) {
return tl::make_unexpected(make_error_code(json_errc::invalid_query));
}

for (auto &[key, value] : json.items()) {

if (value.is_string()) {
result += percent_encode<std::string>(key);
result += equal;
result += percent_encode<std::string>(value.get<std::string>());
result += separator;
}
else if (value.is_array()) {
for (auto &element : value.items()) {
result += percent_encode<std::string>(key);
result += equal;
result += percent_encode<std::string>(element.value().get<std::string>());
result += separator;
}
}
else {
result += percent_encode<std::string>(key);
result += equal;
result += separator;
}
}

return result.substr(0, result.size() - 1);
}

inline auto decode_query(std::string_view query, char separator='&', char equal='=') -> nlohmann::json {
if (query[0] == '?') {
query.remove_prefix(1);
}

nlohmann::json object;
for (auto [key, value] : query_parameter_range(query)) {
const auto key_ = skyr::percent_decode<std::string>(key).value();
const auto value_ = value? skyr::percent_decode<std::string>(value.value()).value() : std::string();

if (object.contains(key_)) {
auto current_value = object[key_];
if (current_value.is_string()) {
auto prev_value = current_value.get<std::string>();
object[key_] = std::vector<std::string>{prev_value, value_};
}
else if (current_value.is_array()) {
auto values = current_value.get<std::vector<std::string>>();
values.emplace_back(value_);
object[key_] = values;
}
}
else {
object[key_] = value_;
}
}
return object;
}
} // namespace json
} // namespace v1
} // namespace skyr

#endif //SKYR_URL_QUERYSTRING_HPP
Loading

0 comments on commit f4631ac

Please sign in to comment.