diff --git a/DEPENDENCIES b/DEPENDENCIES index 4c26a11..9613b62 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,6 +1,6 @@ vendorpull https://github.com/sourcemeta/vendorpull dea311b5bfb53b6926a4140267959ae334d3ecf4 noa https://github.com/sourcemeta/noa 7e26abce7a4e31e86a16ef2851702a56773ca527 -jsontoolkit https://github.com/sourcemeta/jsontoolkit 3ef19daf7ca042544239111c701a51232f3f5576 +jsontoolkit https://github.com/sourcemeta/jsontoolkit 3e3ac593146af68e9bd01f3418a263821f8f47cc hydra https://github.com/sourcemeta/hydra 3c53d3fdef79e9ba603d48470a508cc45472a0dc alterschema https://github.com/sourcemeta/alterschema 744cf03a950b681a61f1f4cf6a7bb55bc52836c9 jsonbinpack https://github.com/sourcemeta/jsonbinpack 43d53dd32c432333deb1aea147095ed8707b5f11 diff --git a/vendor/jsontoolkit/CMakeLists.txt b/vendor/jsontoolkit/CMakeLists.txt index 3853039..37f1c94 100644 --- a/vendor/jsontoolkit/CMakeLists.txt +++ b/vendor/jsontoolkit/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.16) project(jsontoolkit VERSION 2.0.0 LANGUAGES CXX - DESCRIPTION "The swiss-army knife for JSON programming in C++" + DESCRIPTION "The high-performance JSON Schema evaluator and related JSON utilities for modern C++" HOMEPAGE_URL "https://jsontoolkit.sourcemeta.com") list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") include(vendor/noa/cmake/noa.cmake) diff --git a/vendor/jsontoolkit/src/evaluator/context.cc b/vendor/jsontoolkit/src/evaluator/context.cc index 9a9f0fa..699f63b 100644 --- a/vendor/jsontoolkit/src/evaluator/context.cc +++ b/vendor/jsontoolkit/src/evaluator/context.cc @@ -71,7 +71,7 @@ auto EvaluationContext::push(const Pointer &relative_schema_location, relative_instance_location, schema_resource, dynamic); assert(!relative_instance_location.empty()); - this->instances_.emplace_back(std::move(new_instance)); + this->instances_.emplace_back(new_instance); } auto EvaluationContext::pop(const bool dynamic) -> void { @@ -102,69 +102,6 @@ auto EvaluationContext::annotate(const WeakPointer ¤t_instance_location, return {*(result.first), result.second}; } -auto EvaluationContext::annotations( - const WeakPointer ¤t_instance_location, - const WeakPointer &schema_location) const -> const std::set & { - static const decltype(this->annotations_)::mapped_type::mapped_type - placeholder; - // Use `.find()` instead of `.contains()` and `.at()` for performance - // reasons - const auto instance_location_result{ - this->annotations_.find(current_instance_location)}; - if (instance_location_result == this->annotations_.end()) { - return placeholder; - } - - const auto schema_location_result{ - instance_location_result->second.find(schema_location)}; - if (schema_location_result == instance_location_result->second.end()) { - return placeholder; - } - - return schema_location_result->second; -} - -auto EvaluationContext::annotations( - const WeakPointer ¤t_instance_location) const - -> const std::map> & { - static const decltype(this->annotations_)::mapped_type placeholder; - // Use `.find()` instead of `.contains()` and `.at()` for performance - // reasons - const auto instance_location_result{ - this->annotations_.find(current_instance_location)}; - if (instance_location_result == this->annotations_.end()) { - return placeholder; - } - - return instance_location_result->second; -} - -auto EvaluationContext::defines_any_adjacent_annotation( - const WeakPointer &expected_instance_location, - const WeakPointer &base_evaluate_path, const std::string &keyword) const - -> bool { - // TODO: We should be taking masks into account - // TODO: How can we avoid this expensive pointer manipulation? - auto expected_evaluate_path{base_evaluate_path}; - expected_evaluate_path.push_back({keyword}); - return !this->annotations(expected_instance_location, expected_evaluate_path) - .empty(); -} - -auto EvaluationContext::defines_any_adjacent_annotation( - const WeakPointer &expected_instance_location, - const WeakPointer &base_evaluate_path, - const std::vector &keywords) const -> bool { - for (const auto &keyword : keywords) { - if (this->defines_any_adjacent_annotation(expected_instance_location, - base_evaluate_path, keyword)) { - return true; - } - } - - return false; -} - auto EvaluationContext::defines_annotation( const WeakPointer &expected_instance_location, const WeakPointer &base_evaluate_path, @@ -173,10 +110,14 @@ auto EvaluationContext::defines_annotation( return false; } - const auto instance_annotations{ - this->annotations(expected_instance_location)}; + const auto instance_location_result{ + this->annotations_.find(expected_instance_location)}; + if (instance_location_result == this->annotations_.end()) { + return false; + } + for (const auto &[schema_location, schema_annotations] : - instance_annotations) { + instance_location_result->second) { assert(!schema_location.empty()); const auto &keyword{schema_location.back()}; @@ -210,8 +151,15 @@ auto EvaluationContext::largest_annotation_index( // TODO: We should be taking masks into account std::uint64_t result{default_value}; + + const auto instance_location_result{ + this->annotations_.find(expected_instance_location)}; + if (instance_location_result == this->annotations_.end()) { + return result; + } + for (const auto &[schema_location, schema_annotations] : - this->annotations(expected_instance_location)) { + instance_location_result->second) { assert(!schema_location.empty()); const auto &keyword{schema_location.back()}; if (!keyword.is_property()) { @@ -275,20 +223,33 @@ auto EvaluationContext::target_type(const TargetType type) noexcept -> void { auto EvaluationContext::resolve_target() -> const JSON & { if (this->property_as_instance) [[unlikely]] { - assert(!this->instance_location().empty()); - assert(this->instance_location().back().is_property()); - // For efficiency, as we likely reference the same JSON values - // over and over again - // TODO: Get rid of this once we have weak pointers - static std::set property_values; - return *( - property_values.emplace(this->instance_location().back().to_property()) - .first); + // In this case, we still need to return a string in order + // to cope with non-string keywords inside `propertyNames` + // that need to fail validation. But then, the actual string + // we return doesn't matter, so we can always return a dummy one. + static const JSON empty_string{""}; + return empty_string; } return this->instances_.back().get(); } +auto EvaluationContext::resolve_string_target() + -> std::optional> { + if (this->property_as_instance) [[unlikely]] { + assert(!this->instance_location().empty()); + assert(this->instance_location().back().is_property()); + return this->instance_location().back().to_property(); + } else { + const auto &result{this->instances_.back().get()}; + if (!result.is_string()) { + return std::nullopt; + } + + return result.to_string(); + } +} + auto EvaluationContext::mark(const std::size_t id, const SchemaCompilerTemplate &children) -> void { this->labels.try_emplace(id, children); diff --git a/vendor/jsontoolkit/src/evaluator/evaluator.cc b/vendor/jsontoolkit/src/evaluator/evaluator.cc index b54048c..e9d4682 100644 --- a/vendor/jsontoolkit/src/evaluator/evaluator.cc +++ b/vendor/jsontoolkit/src/evaluator/evaluator.cc @@ -44,6 +44,27 @@ auto evaluate_step( } \ bool result{false}; +#define EVALUATE_BEGIN_IF_STRING(step_category, step_type) \ + SOURCEMETA_TRACE_END(trace_dispatch_id, "Dispatch"); \ + SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ + const auto &step_category{std::get(step)}; \ + context.push(step_category.relative_schema_location, \ + step_category.relative_instance_location, \ + step_category.schema_resource, step_category.dynamic); \ + const auto &maybe_target{context.resolve_string_target()}; \ + if (!maybe_target.has_value()) { \ + context.pop(step_category.dynamic); \ + SOURCEMETA_TRACE_END(trace_id, STRINGIFY(step_type)); \ + return true; \ + } \ + if (step_category.report && callback.has_value()) { \ + callback.value()(SchemaCompilerEvaluationType::Pre, true, step, \ + context.evaluate_path(), context.instance_location(), \ + context.null); \ + } \ + const auto &target{maybe_target.value().get()}; \ + bool result{false}; + #define EVALUATE_BEGIN_NO_TARGET(step_category, step_type, precondition) \ SOURCEMETA_TRACE_END(trace_dispatch_id, "Dispatch"); \ SOURCEMETA_TRACE_START(trace_id, STRINGIFY(step_type)); \ @@ -317,23 +338,22 @@ auto evaluate_step( } case IS_STEP(SchemaCompilerAssertionRegex): { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionRegex, - target.is_string()); - result = std::regex_search(target.to_string(), assertion.value.first); + EVALUATE_BEGIN_IF_STRING(assertion, SchemaCompilerAssertionRegex); + result = std::regex_search(target, assertion.value.first); EVALUATE_END(assertion, SchemaCompilerAssertionRegex); } case IS_STEP(SchemaCompilerAssertionStringSizeLess): { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionStringSizeLess, - target.is_string()); - result = (target.size() < assertion.value); + EVALUATE_BEGIN_IF_STRING(assertion, + SchemaCompilerAssertionStringSizeLess); + result = (JSON::size(target) < assertion.value); EVALUATE_END(assertion, SchemaCompilerAssertionStringSizeLess); } case IS_STEP(SchemaCompilerAssertionStringSizeGreater): { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionStringSizeGreater, - target.is_string()); - result = (target.size() > assertion.value); + EVALUATE_BEGIN_IF_STRING(assertion, + SchemaCompilerAssertionStringSizeGreater); + result = (JSON::size(target) > assertion.value); EVALUATE_END(assertion, SchemaCompilerAssertionStringSizeGreater); } @@ -423,13 +443,12 @@ auto evaluate_step( } case IS_STEP(SchemaCompilerAssertionStringType): { - EVALUATE_BEGIN(assertion, SchemaCompilerAssertionStringType, - target.is_string()); + EVALUATE_BEGIN_IF_STRING(assertion, SchemaCompilerAssertionStringType); switch (assertion.value) { case SchemaCompilerValueStringType::URI: try { // TODO: This implies a string copy - result = URI{target.to_string()}.is_absolute(); + result = URI{target}.is_absolute(); } catch (const URIParseError &) { result = false; } @@ -530,39 +549,6 @@ auto evaluate_step( EVALUATE_END(logical, SchemaCompilerLogicalWhenDefines); } - case IS_STEP(SchemaCompilerLogicalWhenAdjacentUnmarked): { - EVALUATE_BEGIN_NO_TARGET( - logical, SchemaCompilerLogicalWhenAdjacentUnmarked, - !context.defines_any_adjacent_annotation(context.instance_location(), - context.evaluate_path(), - logical.value)); - result = true; - for (const auto &child : logical.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - break; - } - } - - EVALUATE_END(logical, SchemaCompilerLogicalWhenAdjacentUnmarked); - } - - case IS_STEP(SchemaCompilerLogicalWhenAdjacentMarked): { - EVALUATE_BEGIN_NO_TARGET(logical, SchemaCompilerLogicalWhenAdjacentMarked, - context.defines_any_adjacent_annotation( - context.instance_location(), - context.evaluate_path(), logical.value)); - result = true; - for (const auto &child : logical.children) { - if (!evaluate_step(child, mode, callback, context)) { - result = false; - break; - } - } - - EVALUATE_END(logical, SchemaCompilerLogicalWhenAdjacentMarked); - } - case IS_STEP(SchemaCompilerLogicalWhenArraySizeGreater): { EVALUATE_BEGIN(logical, SchemaCompilerLogicalWhenArraySizeGreater, target.is_array() && target.size() > logical.value); @@ -593,61 +579,64 @@ auto evaluate_step( case IS_STEP(SchemaCompilerLogicalXor): { EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalXor); - result = false; - - // TODO: Cache results of a given branch so we can avoid - // computing it multiple times - for (auto iterator{logical.children.cbegin()}; - iterator != logical.children.cend(); ++iterator) { - if (!evaluate_step(*iterator, mode, callback, context)) { - continue; - } - - // Check if another one matches - bool subresult{true}; - for (auto subiterator{logical.children.cbegin()}; - subiterator != logical.children.cend(); ++subiterator) { - // Don't compare the element against itself - if (std::distance(logical.children.cbegin(), iterator) == - std::distance(logical.children.cbegin(), subiterator)) { - continue; - } - - // We don't need to report traces that part of the exhaustive - // XOR search. We can treat those as internal - if (evaluate_step(*subiterator, mode, std::nullopt, context)) { - subresult = false; - break; + result = true; + bool has_matched{false}; + for (const auto &child : logical.children) { + if (evaluate_step(child, mode, callback, context)) { + if (has_matched) { + result = false; + if (mode == SchemaCompilerEvaluationMode::Fast) { + break; + } + } else { + has_matched = true; } } - - result = result || subresult; - if (result && mode == SchemaCompilerEvaluationMode::Fast) { - break; - } } + result = result && has_matched; EVALUATE_END(logical, SchemaCompilerLogicalXor); } - case IS_STEP(SchemaCompilerLogicalTryMark): { - EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalTryMark); + case IS_STEP(SchemaCompilerLogicalCondition): { + EVALUATE_BEGIN_NO_PRECONDITION(logical, SchemaCompilerLogicalCondition); result = true; - for (const auto &child : logical.children) { - if (!evaluate_step(child, mode, callback, context)) { + const auto children_size{logical.children.size()}; + assert(children_size >= logical.value.first); + assert(children_size >= logical.value.second); + + auto condition_end{children_size}; + if (logical.value.first > 0) { + condition_end = logical.value.first; + } else if (logical.value.second > 0) { + condition_end = logical.value.second; + } + + for (std::size_t cursor = 0; cursor < condition_end; cursor++) { + if (!evaluate_step(logical.children[cursor], mode, callback, context)) { result = false; break; } } - if (result) { - // TODO: This implies an allocation of a JSON boolean - context.annotate(context.instance_location(), JSON{true}); - } else { - result = true; + const auto consequence_start{result ? logical.value.first + : logical.value.second}; + const auto consequence_end{(result && logical.value.second > 0) + ? logical.value.second + : children_size}; + result = true; + if (consequence_start > 0) { + for (auto cursor = consequence_start; cursor < consequence_end; + cursor++) { + if (!evaluate_step(logical.children[cursor], mode, callback, + context)) { + result = false; + break; + } + } } - EVALUATE_END(logical, SchemaCompilerLogicalTryMark); + EVALUATE_END(logical, SchemaCompilerLogicalCondition); } case IS_STEP(SchemaCompilerLogicalNot): { @@ -1127,6 +1116,7 @@ auto evaluate_step( #undef IS_STEP #undef STRINGIFY #undef EVALUATE_BEGIN +#undef EVALUATE_BEGIN_IF_STRING #undef EVALUATE_BEGIN_NO_TARGET #undef EVALUATE_BEGIN_TRY_TARGET #undef EVALUATE_BEGIN_NO_PRECONDITION diff --git a/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_context.h b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_context.h index c83b231..9a10278 100644 --- a/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_context.h +++ b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_context.h @@ -67,6 +67,8 @@ class SOURCEMETA_JSONTOOLKIT_EVALUATOR_EXPORT EvaluationContext { enum class TargetType : std::uint8_t { Key, Value }; auto target_type(const TargetType type) noexcept -> void; auto resolve_target() -> const JSON &; + auto resolve_string_target() + -> std::optional>; /////////////////////////////////////////////// // References and anchors @@ -91,14 +93,6 @@ class SOURCEMETA_JSONTOOLKIT_EVALUATOR_EXPORT EvaluationContext { auto annotate(const WeakPointer ¤t_instance_location, const JSON &value) -> std::pair, bool>; - auto - defines_any_adjacent_annotation(const WeakPointer &expected_instance_location, - const WeakPointer &base_evaluate_path, - const std::string &keyword) const -> bool; - auto defines_any_adjacent_annotation( - const WeakPointer &expected_instance_location, - const WeakPointer &base_evaluate_path, - const std::vector &keywords) const -> bool; auto defines_annotation(const WeakPointer &expected_instance_location, const WeakPointer &base_evaluate_path, const std::vector &keywords, @@ -108,13 +102,6 @@ class SOURCEMETA_JSONTOOLKIT_EVALUATOR_EXPORT EvaluationContext { const std::uint64_t default_value) const -> std::uint64_t; -private: - auto annotations(const WeakPointer ¤t_instance_location, - const WeakPointer &schema_location) const - -> const std::set &; - auto annotations(const WeakPointer ¤t_instance_location) const - -> const std::map> &; - public: // TODO: Remove this const JSON null{nullptr}; diff --git a/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_template.h b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_template.h index f824f69..9a71442 100644 --- a/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_template.h +++ b/vendor/jsontoolkit/src/evaluator/include/sourcemeta/jsontoolkit/evaluator_template.h @@ -51,12 +51,10 @@ struct SchemaCompilerAnnotationBasenameToParent; struct SchemaCompilerLogicalOr; struct SchemaCompilerLogicalAnd; struct SchemaCompilerLogicalXor; -struct SchemaCompilerLogicalTryMark; +struct SchemaCompilerLogicalCondition; struct SchemaCompilerLogicalNot; struct SchemaCompilerLogicalWhenType; struct SchemaCompilerLogicalWhenDefines; -struct SchemaCompilerLogicalWhenAdjacentUnmarked; -struct SchemaCompilerLogicalWhenAdjacentMarked; struct SchemaCompilerLogicalWhenArraySizeGreater; struct SchemaCompilerLogicalWhenArraySizeEqual; struct SchemaCompilerLoopPropertiesMatch; @@ -104,10 +102,8 @@ using SchemaCompilerTemplate = std::vector, std::vector>; +/// @ingroup evaluator +/// Represents a compiler step value that consists of two indexes +using SchemaCompilerValueIndexPair = std::pair; + } // namespace sourcemeta::jsontoolkit #endif diff --git a/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_value.h b/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_value.h index b33ccd5..e2142b0 100644 --- a/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_value.h +++ b/vendor/jsontoolkit/src/json/include/sourcemeta/jsontoolkit/json_value.h @@ -218,6 +218,18 @@ class SOURCEMETA_JSONTOOLKIT_JSON_EXPORT JSON { /// objects. static auto make_object() -> JSON; + /// This function calculates the logical size of a string according to the + /// JSON specification. For example: + /// + /// ```cpp + /// #include + /// #include + /// + /// const sourcemeta::jsontoolkit::JSON::String value{"foo"}; + /// assert(sourcemeta::jsontoolkit::JSON::size(value) == 3); + /// ``` + static auto size(const String &value) noexcept -> std::size_t; + /* * Operators */ diff --git a/vendor/jsontoolkit/src/json/json_value.cc b/vendor/jsontoolkit/src/json/json_value.cc index a66b61b..19a0010 100644 --- a/vendor/jsontoolkit/src/json/json_value.cc +++ b/vendor/jsontoolkit/src/json/json_value.cc @@ -69,6 +69,24 @@ auto JSON::make_array() -> JSON { return JSON{Array{}}; } auto JSON::make_object() -> JSON { return JSON{Object{}}; } +auto JSON::size(const String &value) noexcept -> std::size_t { + std::size_t result{0}; + + // We want to count the number of logical characters, + // not the number of bytes + for (const auto character : value) { + // In UTF-8, continuation bytes (i.e. not the first) are + // encoded as `10xxxxxx`, so this means we are at the start + // of a code-point + // See https://en.wikipedia.org/wiki/UTF-8#Encoding + if ((character & 0b11000000) != 0b10000000) { + result += 1; + } + } + + return result; +} + auto JSON::operator<(const JSON &other) const noexcept -> bool { if ((this->type() == Type::Integer && other.type() == Type::Real) || (this->type() == Type::Real && other.type() == Type::Integer)) { @@ -286,12 +304,6 @@ auto JSON::operator-=(const JSON &substractive) -> JSON & { [[nodiscard]] auto JSON::at(const typename JSON::Array::size_type index) const -> const JSON & { - // In practice, this case only applies in some edge cases when - // using JSON Pointers - if (this->is_object()) [[unlikely]] { - return this->at(std::to_string(index)); - } - assert(this->is_array()); assert(index < this->size()); return std::get(this->data).data.at(index); @@ -299,12 +311,6 @@ auto JSON::operator-=(const JSON &substractive) -> JSON & { [[nodiscard]] auto JSON::at(const typename JSON::Array::size_type index) -> JSON & { - // In practice, this case only applies in some edge cases when - // using JSON Pointers - if (this->is_object()) [[unlikely]] { - return this->at(std::to_string(index)); - } - assert(this->is_array()); assert(index < this->size()); return std::get(this->data).data.at(index); @@ -353,21 +359,7 @@ auto JSON::operator-=(const JSON &substractive) -> JSON & { return std::get(this->data).data.size(); } else { assert(this->is_string()); - std::size_t result{0}; - - // We want to count the number of logical characters, - // not the number of bytes - for (const auto character : std::get(this->data)) { - // In UTF-8, continuation bytes (i.e. not the first) are - // encoded as `10xxxxxx`, so this means we are at the start - // of a code-point - // See https://en.wikipedia.org/wiki/UTF-8#Encoding - if ((character & 0b11000000) != 0b10000000) { - result += 1; - } - } - - return result; + return JSON::size(std::get(this->data)); } } diff --git a/vendor/jsontoolkit/src/json/parser.h b/vendor/jsontoolkit/src/json/parser.h index 9720ac1..88253c6 100644 --- a/vendor/jsontoolkit/src/json/parser.h +++ b/vendor/jsontoolkit/src/json/parser.h @@ -71,12 +71,10 @@ inline auto parse_boolean_false( return JSON{false}; } -auto parse_string_unicode( +auto parse_string_unicode_code_point( const std::uint64_t line, std::uint64_t &column, - std::basic_istream &stream, - std::basic_ostringstream> - &result) -> void { + std::basic_istream &stream) + -> unsigned long { std::basic_string> code_point; @@ -109,8 +107,68 @@ auto parse_string_unicode( // According to ECMA 404, \u can be followed by "any" // sequence of 4 hexadecimal digits. constexpr auto unicode_base{16}; - result.put(static_cast( - std::stoul(code_point, nullptr, unicode_base))); + const auto result{std::stoul(code_point, nullptr, unicode_base)}; + // The largest possible valid unicode code point + assert(result <= 0xFFFF); + return result; +} + +auto parse_string_unicode( + const std::uint64_t line, std::uint64_t &column, + std::basic_istream &stream, + std::basic_ostringstream> + &result) -> void { + auto code_point{parse_string_unicode_code_point(line, column, stream)}; + using CharT = typename JSON::Char; + + // This means we are at the beginning of a UTF-16 surrogate pair high code + // point See + // https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF + if (code_point >= 0xD800 && code_point <= 0xDBFF) { + // Next, we expect "\" + column += 1; + if (stream.get() != internal::token_string_escape) { + throw ParseError(line, column); + } + + // Next, we expect "u" + column += 1; + if (stream.get() != internal::token_string_escape_unicode) { + throw ParseError(line, column); + } + + // Finally, get the low code point of the surrogate and calculate + // the real final code point + const auto low_code_point{ + parse_string_unicode_code_point(line, column, stream)}; + + // See + // https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF + if (low_code_point >= 0xDC00 && low_code_point <= 0xDFFF) { + code_point = + 0x10000 + ((code_point - 0xD800) << 10) + (low_code_point - 0xDC00); + } else { + throw ParseError(line, column); + } + } + + // Convert a Unicode codepoint into UTF-8 + // See https://en.wikipedia.org/wiki/UTF-8#Description + + if (code_point <= 0x7F) { + // UTF-8 + result.put(static_cast(code_point)); + } else if (code_point <= 0x7FF) { + // UTF-16 + result.put(static_cast(0xC0 | ((code_point >> 6) & 0x1F))); + result.put(static_cast(0x80 | (code_point & 0x3F))); + } else { + // UTF-32 + result.put(static_cast(0xE0 | ((code_point >> 12) & 0x0F))); + result.put(static_cast(0x80 | ((code_point >> 6) & 0x3F))); + result.put(static_cast(0x80 | (code_point & 0x3F))); + } } auto parse_string_escape( @@ -596,7 +654,7 @@ auto internal_parse( std::uint64_t &line, std::uint64_t &column) -> JSON { // Globals using Result = JSON; - enum class Container { Array, Object }; + enum class Container : std::uint8_t { Array, Object }; std::stack levels; std::stack> frames; std::optional result; diff --git a/vendor/jsontoolkit/src/jsonpointer/jsonpointer.cc b/vendor/jsontoolkit/src/jsonpointer/jsonpointer.cc index 8530a6a..0d3ccbb 100644 --- a/vendor/jsontoolkit/src/jsonpointer/jsonpointer.cc +++ b/vendor/jsontoolkit/src/jsonpointer/jsonpointer.cc @@ -47,7 +47,11 @@ auto traverse(V &document, typename PointerT::const_iterator begin, // array element with the zero-based index identified by the // token. // See https://www.rfc-editor.org/rfc/rfc6901#section-4 - current = current.get().at(iterator->to_index()); + if (current.get().is_object()) { + current = current.get().at(std::to_string(iterator->to_index())); + } else { + current = current.get().at(iterator->to_index()); + } } } @@ -66,17 +70,28 @@ auto try_traverse(const sourcemeta::jsontoolkit::JSON &document, const auto &instance{current.get()}; if (token.is_property()) { const auto &property{token.to_property()}; + if (!instance.is_object()) { + return std::nullopt; + } + auto json_value{instance.try_at(property)}; if (json_value.has_value()) { current = std::move(json_value.value()); - } else { return std::nullopt; } } else { + if (!instance.is_array() && !instance.is_object()) { + return std::nullopt; + } + const auto index{token.to_index()}; if (index < instance.size()) { - current = instance.at(index); + if (instance.is_object()) { + current = instance.at(std::to_string(index)); + } else { + current = instance.at(index); + } } else { return std::nullopt; } @@ -161,7 +176,11 @@ auto set(JSON &document, const Pointer &pointer, const JSON &value) -> void { } else if (last.is_property()) { current.at(last.to_property()).into(value); } else { - current.at(last.to_index()).into(value); + if (current.is_object()) { + current.at(std::to_string(last.to_index())).into(value); + } else { + current.at(last.to_index()).into(value); + } } } diff --git a/vendor/jsontoolkit/src/jsonschema/compile_describe.cc b/vendor/jsontoolkit/src/jsonschema/compile_describe.cc index 04354c2..a65f49d 100644 --- a/vendor/jsontoolkit/src/jsonschema/compile_describe.cc +++ b/vendor/jsontoolkit/src/jsonschema/compile_describe.cc @@ -216,12 +216,8 @@ struct DescribeVisitor { return message.str(); } - auto operator()(const SchemaCompilerLogicalTryMark &) const -> std::string { - assert(this->keyword == "if"); - std::ostringstream message; - message << "The " << to_string(this->target.type()) - << " value was tested against the conditional subschema"; - return message.str(); + auto operator()(const SchemaCompilerLogicalCondition &) const -> std::string { + return unknown(); } auto operator()(const SchemaCompilerLogicalNot &) const -> std::string { @@ -1629,46 +1625,6 @@ struct DescribeVisitor { return unknown(); } - auto operator()(const SchemaCompilerLogicalWhenAdjacentUnmarked &step) const - -> std::string { - if (this->keyword == "else") { - assert(!step.children.empty()); - std::ostringstream message; - message << "Because of the conditional outcome, the " - << to_string(this->target.type()) - << " value was expected to validate against the "; - if (step.children.size() > 1) { - message << step.children.size() << " given subschemas"; - } else { - message << "given subschema"; - } - - return message.str(); - } - - return unknown(); - } - - auto operator()(const SchemaCompilerLogicalWhenAdjacentMarked &step) const - -> std::string { - if (this->keyword == "then") { - assert(!step.children.empty()); - std::ostringstream message; - message << "Because of the conditional outcome, the " - << to_string(this->target.type()) - << " value was expected to validate against the "; - if (step.children.size() > 1) { - message << step.children.size() << " given subschemas"; - } else { - message << "given subschema"; - } - - return message.str(); - } - - return unknown(); - } - auto operator()(const SchemaCompilerAssertionPropertyDependencies &step) const -> std::string { if (this->keyword == "dependentRequired") { diff --git a/vendor/jsontoolkit/src/jsonschema/compile_json.cc b/vendor/jsontoolkit/src/jsonschema/compile_json.cc index c707100..89ad1be 100644 --- a/vendor/jsontoolkit/src/jsonschema/compile_json.cc +++ b/vendor/jsontoolkit/src/jsonschema/compile_json.cc @@ -158,6 +158,13 @@ auto value_to_json(const T &value) -> sourcemeta::jsontoolkit::JSON { assert(false); } + return result; + } else if constexpr (std::is_same_v) { + result.assign("type", JSON{"index-pair"}); + JSON data{JSON::make_array()}; + data.push_back(JSON{value.first}); + data.push_back(JSON{value.second}); + result.assign("value", std::move(data)); return result; } else { static_assert(std::is_same_v); @@ -259,14 +266,10 @@ struct StepVisitor { HANDLE_STEP("logical", "or", SchemaCompilerLogicalOr) HANDLE_STEP("logical", "and", SchemaCompilerLogicalAnd) HANDLE_STEP("logical", "xor", SchemaCompilerLogicalXor) - HANDLE_STEP("logical", "try-mark", SchemaCompilerLogicalTryMark) + HANDLE_STEP("logical", "condition", SchemaCompilerLogicalCondition) HANDLE_STEP("logical", "not", SchemaCompilerLogicalNot) HANDLE_STEP("logical", "when-type", SchemaCompilerLogicalWhenType) HANDLE_STEP("logical", "when-defines", SchemaCompilerLogicalWhenDefines) - HANDLE_STEP("logical", "when-adjacent-unmarked", - SchemaCompilerLogicalWhenAdjacentUnmarked) - HANDLE_STEP("logical", "when-adjacent-marked", - SchemaCompilerLogicalWhenAdjacentMarked) HANDLE_STEP("logical", "when-array-size-greater", SchemaCompilerLogicalWhenArraySizeGreater) HANDLE_STEP("logical", "when-array-size-equal", diff --git a/vendor/jsontoolkit/src/jsonschema/default_compiler.cc b/vendor/jsontoolkit/src/jsonschema/default_compiler.cc index 06dcbdf..443804b 100644 --- a/vendor/jsontoolkit/src/jsonschema/default_compiler.cc +++ b/vendor/jsontoolkit/src/jsonschema/default_compiler.cc @@ -499,7 +499,8 @@ auto sourcemeta::jsontoolkit::default_schema_compiler( "https://json-schema.org/draft/2019-09/vocab/core") || schema_context.vocabularies.contains( "https://json-schema.org/draft/2020-12/vocab/core")) && - !dynamic_context.keyword.starts_with('$')) { + !dynamic_context.keyword.starts_with('$') && + dynamic_context.keyword != "definitions") { // We handle these keywords as part of "contains" if ((schema_context.vocabularies.contains( diff --git a/vendor/jsontoolkit/src/jsonschema/default_compiler_draft4.h b/vendor/jsontoolkit/src/jsonschema/default_compiler_draft4.h index 79aada2..df2c31d 100644 --- a/vendor/jsontoolkit/src/jsonschema/default_compiler_draft4.h +++ b/vendor/jsontoolkit/src/jsonschema/default_compiler_draft4.h @@ -6,12 +6,27 @@ #include // std::sort, std::any_of #include // assert -#include // std::regex +#include // std::regex, std::regex_error #include // std::set +#include // std::ostringstream #include // std::move #include "compile_helpers.h" +static auto parse_regex(const std::string &pattern, + const sourcemeta::jsontoolkit::URI &base, + const sourcemeta::jsontoolkit::Pointer &schema_location) + -> std::regex { + try { + return std::regex{pattern, std::regex::ECMAScript | std::regex::nosubs}; + } catch (const std::regex_error &) { + std::ostringstream message; + message << "Invalid regular expression: " << pattern; + throw sourcemeta::jsontoolkit::SchemaCompilationError(base, schema_location, + message.str()); + } +} + namespace internal { using namespace sourcemeta::jsontoolkit; @@ -46,6 +61,16 @@ auto compiler_draft4_core_ref( auto new_schema_context{schema_context}; new_schema_context.references.insert(reference.destination); + std::size_t direct_children_references{0}; + if (context.frame.contains({type, reference.destination})) { + for (const auto &reference_entry : context.references) { + if (reference_entry.first.second.starts_with( + context.frame.at({type, reference.destination}).pointer)) { + direct_children_references += 1; + } + } + } + // If the reference is not a recursive one, we can avoid the extra // overhead of marking the location for future jumps, and pretty much // just expand the reference destination in place. @@ -56,7 +81,8 @@ auto compiler_draft4_core_ref( entry.pointer.starts_with( context.frame.at({type, reference.destination}).pointer)) || schema_context.references.contains(reference.destination)}; - if (!is_recursive) { + + if (!is_recursive && direct_children_references <= 5) { // TODO: Enable this optimization for 2019-09 on-wards if (schema_context.vocabularies.contains( "http://json-schema.org/draft-04/schema#") || @@ -359,7 +385,7 @@ auto compiler_draft4_applicator_properties_conditional_annotation( } const auto size{schema_context.schema.at(dynamic_context.keyword).size()}; - const auto imports_required_keyword = + const auto imports_validation_vocabulary = schema_context.vocabularies.contains( "http://json-schema.org/draft-04/schema#") || schema_context.vocabularies.contains( @@ -371,7 +397,8 @@ auto compiler_draft4_applicator_properties_conditional_annotation( schema_context.vocabularies.contains( "https://json-schema.org/draft/2020-12/vocab/validation"); std::set required; - if (imports_required_keyword && schema_context.schema.defines("required") && + if (imports_validation_vocabulary && + schema_context.schema.defines("required") && schema_context.schema.at("required").is_array()) { for (const auto &property : schema_context.schema.at("required").as_array()) { @@ -385,40 +412,62 @@ auto compiler_draft4_applicator_properties_conditional_annotation( } std::size_t is_required = 0; - std::vector properties; + std::vector> properties; for (const auto &entry : schema_context.schema.at(dynamic_context.keyword).as_object()) { - properties.push_back(entry.first); + properties.push_back( + {entry.first, compile(context, schema_context, relative_dynamic_context, + {entry.first}, {entry.first})}); if (required.contains(entry.first)) { is_required += 1; } } - // To guarantee order - std::sort(properties.begin(), properties.end()); + // In many cases, `properties` have some subschemas that are small + // and some subschemas that are large. To attempt to improve performance, + // we prefer to evaluate smaller subschemas first, in the hope of failing + // earlier without spending a lot of time on other subschemas + std::sort(properties.begin(), properties.end(), + [](const auto &left, const auto &right) { + return (left.second.size() == right.second.size()) + ? (left.first < right.first) + : (left.second.size() < right.second.size()); + }); + + assert(schema_context.relative_pointer.back().is_property()); + assert(schema_context.relative_pointer.back().to_property() == + dynamic_context.keyword); + const auto relative_pointer_size{schema_context.relative_pointer.size()}; + const auto is_directly_inside_oneof{ + relative_pointer_size > 2 && + schema_context.relative_pointer.at(relative_pointer_size - 2) + .is_index() && + schema_context.relative_pointer.at(relative_pointer_size - 3) + .is_property() && + schema_context.relative_pointer.at(relative_pointer_size - 3) + .to_property() == "oneOf"}; // There are two ways to compile `properties` depending on whether // most of the properties are marked as required using `required` // or whether most of the properties are optional. Each shines // in the corresponding case. - const auto prefer_loop_over_instance{ // This strategy only makes sense if most of the properties are "optional" is_required <= (size / 2) && // If `properties` only defines a relatively small amount of properties, // then its probably still faster to unroll - schema_context.schema.at(dynamic_context.keyword).size() > 5}; + schema_context.schema.at(dynamic_context.keyword).size() > 5 && + // Always unroll inside `oneOf`, to have a better chance at + // short-circuiting quickly + !is_directly_inside_oneof}; if (prefer_loop_over_instance) { SchemaCompilerValueNamedIndexes indexes; SchemaCompilerTemplate children; std::size_t cursor = 0; - for (const auto &name : properties) { + for (auto &&[name, substeps] : properties) { indexes.emplace(name, cursor); - auto substeps{compile(context, schema_context, relative_dynamic_context, - {name}, {name})}; - if (annotate) { substeps.push_back(make( true, context, schema_context, relative_dynamic_context, @@ -439,17 +488,21 @@ auto compiler_draft4_applicator_properties_conditional_annotation( SchemaCompilerTemplate children; - for (const auto &name : properties) { - auto substeps{compile(context, schema_context, relative_dynamic_context, - {name}, {name})}; - + for (auto &&[name, substeps] : properties) { if (annotate) { substeps.push_back(make( true, context, schema_context, relative_dynamic_context, JSON{name})); } + const auto assume_object{imports_validation_vocabulary && + schema_context.schema.defines("type") && + schema_context.schema.at("type").is_string() && + schema_context.schema.at("type").to_string() == + "object"}; + // We can avoid this "defines" condition if the property is a required one - if (imports_required_keyword && schema_context.schema.defines("required") && + if (imports_validation_vocabulary && assume_object && + schema_context.schema.defines("required") && schema_context.schema.at("required").is_array() && schema_context.schema.at("required").contains(JSON{name})) { // We can avoid the container too and just inline these steps @@ -573,7 +626,8 @@ auto compiler_draft4_applicator_patternproperties_conditional_annotation( children.push_back(make( // Treat this as an internal step false, context, schema_context, relative_dynamic_context, - SchemaCompilerValueRegex{std::regex{pattern, std::regex::ECMAScript}, + SchemaCompilerValueRegex{parse_regex(pattern, schema_context.base, + schema_context.relative_pointer), pattern}, std::move(substeps))); } @@ -634,7 +688,10 @@ auto compiler_draft4_applicator_additionalproperties_conditional_annotation( for (const auto &entry : schema_context.schema.at("patternProperties").as_object()) { filter.second.push_back( - {std::regex{entry.first, std::regex::ECMAScript}, entry.first}); + {parse_regex(entry.first, schema_context.base, + schema_context.relative_pointer.initial().concat( + {"patternProperties"})), + entry.first}); } } @@ -693,7 +750,8 @@ auto compiler_draft4_validation_pattern( schema_context.schema.at(dynamic_context.keyword).to_string()}; return {make( true, context, schema_context, dynamic_context, - SchemaCompilerValueRegex{std::regex{regex_string, std::regex::ECMAScript}, + SchemaCompilerValueRegex{parse_regex(regex_string, schema_context.base, + schema_context.relative_pointer), regex_string})}; } @@ -732,9 +790,10 @@ auto compiler_draft4_validation_format( if (format == (name)) { \ return {make( \ true, context, schema_context, dynamic_context, \ - SchemaCompilerValueRegex{ \ - std::regex{(regular_expression), std::regex::ECMAScript}, \ - (regular_expression)})}; \ + SchemaCompilerValueRegex{parse_regex(regular_expression, \ + schema_context.base, \ + schema_context.relative_pointer), \ + (regular_expression)})}; \ } COMPILE_FORMAT_REGEX("ipv4", FORMAT_REGEX_IPV4) diff --git a/vendor/jsontoolkit/src/jsonschema/default_compiler_draft7.h b/vendor/jsontoolkit/src/jsonschema/default_compiler_draft7.h index 51f9ac1..1bfd6a6 100644 --- a/vendor/jsontoolkit/src/jsonschema/default_compiler_draft7.h +++ b/vendor/jsontoolkit/src/jsonschema/default_compiler_draft7.h @@ -9,51 +9,76 @@ namespace internal { using namespace sourcemeta::jsontoolkit; +// TODO: Don't generate `if` if neither `then` nor `else` is defined auto compiler_draft7_applicator_if( const SchemaCompilerContext &context, const SchemaCompilerSchemaContext &schema_context, const SchemaCompilerDynamicContext &dynamic_context) -> SchemaCompilerTemplate { - return {make( - true, context, schema_context, dynamic_context, SchemaCompilerValueNone{}, - compile(context, schema_context, relative_dynamic_context, empty_pointer, - empty_pointer))}; -} + // `if` + SchemaCompilerTemplate children{compile( + context, schema_context, dynamic_context, empty_pointer, empty_pointer)}; -auto compiler_draft7_applicator_then( - const SchemaCompilerContext &context, - const SchemaCompilerSchemaContext &schema_context, - const SchemaCompilerDynamicContext &dynamic_context) - -> SchemaCompilerTemplate { - assert(schema_context.schema.is_object()); + // `then` + std::size_t then_cursor{0}; + if (schema_context.schema.defines("then")) { + then_cursor = children.size(); + const auto destination{ + to_uri(schema_context.relative_pointer.initial().concat({"then"}), + schema_context.base) + .recompose()}; + assert(context.frame.contains({ReferenceType::Static, destination})); + for (auto &&step : + compile(context, schema_context, relative_dynamic_context, {"then"}, + empty_pointer, destination)) { + children.push_back(std::move(step)); + } + + // In this case, `if` did nothing, so we can short-circuit + if (then_cursor == 0) { + return children; + } + } - // Nothing to do here - if (!schema_context.schema.defines("if")) { - return {}; + // `else` + std::size_t else_cursor{0}; + if (schema_context.schema.defines("else")) { + else_cursor = children.size(); + const auto destination{ + to_uri(schema_context.relative_pointer.initial().concat({"else"}), + schema_context.base) + .recompose()}; + assert(context.frame.contains({ReferenceType::Static, destination})); + for (auto &&step : + compile(context, schema_context, relative_dynamic_context, {"else"}, + empty_pointer, destination)) { + children.push_back(std::move(step)); + } } - return {make( - true, context, schema_context, dynamic_context, "if", - compile(context, schema_context, relative_dynamic_context, empty_pointer, - empty_pointer))}; + return {make( + false, context, + {schema_context.relative_pointer.initial(), schema_context.schema, + schema_context.vocabularies, schema_context.base, schema_context.labels, + schema_context.references}, + relative_dynamic_context, {then_cursor, else_cursor}, + std::move(children))}; } -auto compiler_draft7_applicator_else( - const SchemaCompilerContext &context, - const SchemaCompilerSchemaContext &schema_context, - const SchemaCompilerDynamicContext &dynamic_context) +// We handle `then` as part of `if` +auto compiler_draft7_applicator_then(const SchemaCompilerContext &, + const SchemaCompilerSchemaContext &, + const SchemaCompilerDynamicContext &) -> SchemaCompilerTemplate { - assert(schema_context.schema.is_object()); - - // Nothing to do here - if (!schema_context.schema.defines("if")) { - return {}; - } + return {}; +} - return {make( - true, context, schema_context, dynamic_context, "if", - compile(context, schema_context, relative_dynamic_context, empty_pointer, - empty_pointer))}; +// We handle `else` as part of `if` +auto compiler_draft7_applicator_else(const SchemaCompilerContext &, + const SchemaCompilerSchemaContext &, + const SchemaCompilerDynamicContext &) + -> SchemaCompilerTemplate { + return {}; } } // namespace internal diff --git a/vendor/jsontoolkit/src/jsonschema/default_walker.cc b/vendor/jsontoolkit/src/jsonschema/default_walker.cc index 516b369..3a503d8 100644 --- a/vendor/jsontoolkit/src/jsonschema/default_walker.cc +++ b/vendor/jsontoolkit/src/jsonschema/default_walker.cc @@ -55,6 +55,10 @@ auto sourcemeta::jsontoolkit::default_schema_walker( WALK(HTTPS_BASE "2020-12/vocab/validation", "maximum", None, "type") WALK(HTTPS_BASE "2020-12/vocab/validation", "minimum", None, "type") + // JSON Schema still defines this for backwards-compatibility + // See https://json-schema.org/draft/2020-12/schema + WALK(HTTPS_BASE "2020-12/vocab/core", "definitions", Members) + // 2019-09 WALK(HTTPS_BASE "2019-09/vocab/core", "$defs", Members) WALK(HTTPS_BASE "2019-09/vocab/applicator", "items", ValueOrElements) @@ -94,6 +98,10 @@ auto sourcemeta::jsontoolkit::default_schema_walker( WALK(HTTPS_BASE "2019-09/vocab/validation", "maximum", None, "type") WALK(HTTPS_BASE "2019-09/vocab/validation", "minimum", None, "type") + // JSON Schema still defines this for backwards-compatibility + // See https://json-schema.org/draft/2019-09/schema + WALK(HTTPS_BASE "2019-09/vocab/core", "definitions", Members) + #undef HTTPS_BASE #define HTTP_BASE "http://json-schema.org/" diff --git a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_error.h b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_error.h index 76f1b10..0a36802 100644 --- a/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_error.h +++ b/vendor/jsontoolkit/src/jsonschema/include/sourcemeta/jsontoolkit/jsonschema_error.h @@ -4,6 +4,7 @@ #include "jsonschema_export.h" #include +#include #include // std::exception #include // std::string @@ -99,6 +100,33 @@ class SOURCEMETA_JSONTOOLKIT_JSONSCHEMA_EXPORT SchemaReferenceError std::string message_; }; +/// @ingroup jsonschema +/// An error that represents a schema compilation failure event +class SOURCEMETA_JSONTOOLKIT_JSONSCHEMA_EXPORT SchemaCompilationError + : public std::exception { +public: + SchemaCompilationError(const URI &base, const Pointer &schema_location, + std::string message) + : base_{base}, schema_location_{schema_location}, + message_{std::move(message)} {} + [[nodiscard]] auto what() const noexcept -> const char * override { + return this->message_.c_str(); + } + + [[nodiscard]] auto base() const noexcept -> const URI & { + return this->base_; + } + + [[nodiscard]] auto location() const noexcept -> const Pointer & { + return this->schema_location_; + } + +private: + URI base_; + Pointer schema_location_; + std::string message_; +}; + #if defined(_MSC_VER) #pragma warning(default : 4251 4275) #endif diff --git a/vendor/jsontoolkit/src/jsonschema/walker.cc b/vendor/jsontoolkit/src/jsonschema/walker.cc index 1119bc1..23856cf 100644 --- a/vendor/jsontoolkit/src/jsonschema/walker.cc +++ b/vendor/jsontoolkit/src/jsonschema/walker.cc @@ -2,9 +2,10 @@ #include #include -#include // std::max, std::sort +#include // std::max #include // assert #include // std::accumulate +#include // std::ranges::sort auto sourcemeta::jsontoolkit::keyword_priority( std::string_view keyword, const std::map &vocabularies, @@ -207,8 +208,8 @@ sourcemeta::jsontoolkit::SchemaKeywordIterator::SchemaKeywordIterator( } // Sort keywords based on priority for correct evaluation - std::sort( - this->entries.begin(), this->entries.end(), + std::ranges::sort( + this->entries, [&vocabularies, &walker](const auto &left, const auto &right) -> bool { // These cannot be empty or indexes, as we created // the entries array from a JSON object diff --git a/vendor/jsontoolkit/src/uri/uri.cc b/vendor/jsontoolkit/src/uri/uri.cc index 27b5160..31c6483 100644 --- a/vendor/jsontoolkit/src/uri/uri.cc +++ b/vendor/jsontoolkit/src/uri/uri.cc @@ -133,7 +133,7 @@ URI::URI(URI &&other) this->scheme_ = std::move(other.scheme_); this->userinfo_ = std::move(other.userinfo_); this->host_ = std::move(other.host_); - this->port_ = std::move(other.port_); + this->port_ = other.port_; this->fragment_ = std::move(other.fragment_); this->query_ = std::move(other.query_);