From c963acce65777107b43d4e0feb576bdb4d84caea Mon Sep 17 00:00:00 2001 From: ras0219 <533828+ras0219@users.noreply.github.com> Date: Mon, 12 Oct 2020 10:22:47 -0700 Subject: [PATCH] [vcpkg] Further JSON error improvements (#13399) * [vcpkg] Split vcpkg/base/json.h into vcpkg/base/jsonreader.h * [vcpkg] Extract definitions of Configuration-Deserializer (& friends) These types are only used by VcpkgPaths during the initial parse. * [vcpkg] Introduce levenshtein-distance suggestions for json errors * [vcpkg] Fix regression in supports handling * [vcpkg] Fix signed/unsigned mismatch * [vcpkg] Address CR comments * [vcpkg] Address CR comments * Fix compiler error from merge conflict. * [vcpkg] Change parameters of Reader::check_for_unexpected_fields to better match declaration * [vcpkg] Improve errors from features set * [vcpkg] Fix includes * [vcpkg] Reuse code * [vcpkg] Check the "name" field always to maximize error information * [docs] Improve english phrasing in manifests.md * [vcpkg] Correct docs link for manifests Co-authored-by: Robert Schumacher Co-authored-by: Billy Robert O'Neal III --- include/vcpkg/base/fwd/json.h | 34 -- include/vcpkg/base/fwd/span.h | 3 + include/vcpkg/base/json.h | 377 +--------------------- include/vcpkg/base/jsonreader.h | 347 ++++++++++++++++++++ include/vcpkg/base/span.h | 2 + include/vcpkg/base/strings.h | 11 + include/vcpkg/base/view.h | 6 - include/vcpkg/configuration.h | 25 -- include/vcpkg/configurationdeserializer.h | 66 ++++ include/vcpkg/registries.h | 34 +- src/vcpkg-test/manifests.cpp | 11 + src/vcpkg-test/strings.cpp | 15 + src/vcpkg/base/json.cpp | 101 +++++- src/vcpkg/base/strings.cpp | 49 +++ src/vcpkg/configuration.cpp | 12 +- src/vcpkg/install.cpp | 5 +- src/vcpkg/platform-expression.cpp | 2 +- src/vcpkg/registries.cpp | 25 +- src/vcpkg/sourceparagraph.cpp | 160 +++++---- src/vcpkg/vcpkgpaths.cpp | 6 +- 20 files changed, 722 insertions(+), 569 deletions(-) create mode 100644 include/vcpkg/base/jsonreader.h create mode 100644 include/vcpkg/configurationdeserializer.h diff --git a/include/vcpkg/base/fwd/json.h b/include/vcpkg/base/fwd/json.h index 73826127ccba03..bb31a5f60ae4db 100644 --- a/include/vcpkg/base/fwd/json.h +++ b/include/vcpkg/base/fwd/json.h @@ -1,9 +1,5 @@ #pragma once -#include -#include -#include - namespace vcpkg::Json { struct JsonStyle; @@ -11,34 +7,4 @@ namespace vcpkg::Json struct Value; struct Object; struct Array; - - struct ReaderError; - struct BasicReaderError; - struct Reader; - - // This is written all the way out so that one can include a subclass in a header - template - struct IDeserializer - { - using type = Type; - virtual StringView type_name() const = 0; - - virtual Span valid_fields() const; - - virtual Optional visit_null(Reader&); - virtual Optional visit_boolean(Reader&, bool); - virtual Optional visit_integer(Reader& r, int64_t i); - virtual Optional visit_number(Reader&, double); - virtual Optional visit_string(Reader&, StringView); - virtual Optional visit_array(Reader&, const Array&); - virtual Optional visit_object(Reader&, const Object&); - - protected: - IDeserializer() = default; - IDeserializer(const IDeserializer&) = default; - IDeserializer& operator=(const IDeserializer&) = default; - IDeserializer(IDeserializer&&) = default; - IDeserializer& operator=(IDeserializer&&) = default; - virtual ~IDeserializer() = default; - }; } diff --git a/include/vcpkg/base/fwd/span.h b/include/vcpkg/base/fwd/span.h index af78aa642a9457..9cd45250ed852c 100644 --- a/include/vcpkg/base/fwd/span.h +++ b/include/vcpkg/base/fwd/span.h @@ -4,4 +4,7 @@ namespace vcpkg { template struct Span; + + template + using View = Span; } diff --git a/include/vcpkg/base/json.h b/include/vcpkg/base/json.h index a16e9ed82cb393..e7f3076f78d319 100644 --- a/include/vcpkg/base/json.h +++ b/include/vcpkg/base/json.h @@ -184,6 +184,7 @@ namespace vcpkg::Json private: underlying_t underlying_; }; + struct Object { private: @@ -287,381 +288,6 @@ namespace vcpkg::Json underlying_t underlying_; }; - VCPKG_MSVC_WARNING(push) - VCPKG_MSVC_WARNING(disable : 4505) - - template - Span IDeserializer::valid_fields() const - { - return {}; - } - - template - Optional IDeserializer::visit_null(Reader&) - { - return nullopt; - } - template - Optional IDeserializer::visit_boolean(Reader&, bool) - { - return nullopt; - } - template - Optional IDeserializer::visit_integer(Reader& r, int64_t i) - { - return this->visit_number(r, static_cast(i)); - } - template - Optional IDeserializer::visit_number(Reader&, double) - { - return nullopt; - } - template - Optional IDeserializer::visit_string(Reader&, StringView) - { - return nullopt; - } - template - Optional IDeserializer::visit_array(Reader&, const Array&) - { - return nullopt; - } - template - Optional IDeserializer::visit_object(Reader&, const Object&) - { - return nullopt; - } - - VCPKG_MSVC_WARNING(pop) - - struct Reader - { - const std::vector& errors() const { return m_errors; } - std::vector& errors() { return m_errors; } - - void add_missing_field_error(StringView type, StringView key, StringView key_type) - { - m_errors.push_back( - Strings::concat(path(), " (", type, "): ", "missing required field '", key, "' (", key_type, ")")); - } - void add_expected_type_error(StringView expected_type) - { - m_errors.push_back(Strings::concat(path(), ": mismatched type: expected ", expected_type)); - } - void add_extra_fields_error(StringView type, std::vector&& fields) - { - for (auto&& field : fields) - m_errors.push_back(Strings::concat(path(), " (", type, "): ", "unexpected field '", field, '\'')); - } - - std::string path() const noexcept; - - private: - std::vector m_errors; - struct Path - { - constexpr Path() = default; - constexpr Path(int64_t i) : index(i) { } - constexpr Path(StringView f) : field(f) { } - - int64_t index = -1; - StringView field; - }; - std::vector m_path; - - template - Optional internal_visit(const Value& value, IDeserializer& visitor) - { - switch (value.kind()) - { - case ValueKind::Null: return visitor.visit_null(*this); - case ValueKind::Boolean: return visitor.visit_boolean(*this, value.boolean()); - case ValueKind::Integer: return visitor.visit_integer(*this, value.integer()); - case ValueKind::Number: return visitor.visit_number(*this, value.number()); - case ValueKind::String: return visitor.visit_string(*this, value.string()); - case ValueKind::Array: return visitor.visit_array(*this, value.array()); - case ValueKind::Object: - { - const auto& obj = value.object(); - check_for_unexpected_fields(obj, visitor.valid_fields(), visitor.type_name()); - return visitor.visit_object(*this, obj); - } - } - - vcpkg::Checks::unreachable(VCPKG_LINE_INFO); - } - - // returns whether the field was found, not whether it was valid - template - bool internal_field(const Object& obj, StringView key, Type& place, IDeserializer& visitor) - { - auto value = obj.get(key); - if (!value) - { - return false; - } - - m_path.push_back(key); - Optional opt = internal_visit(*value, visitor); - - if (auto val = opt.get()) - { - place = std::move(*val); - } - else - { - add_expected_type_error(visitor.type_name().to_string()); - } - m_path.pop_back(); - return true; - } - - // checks that an object doesn't contain any fields which both: - // * don't start with a `$` - // * are not in `valid_fields` - // if known_fields.empty(), then it's treated as if all field names are valid - void check_for_unexpected_fields(const Object& obj, Span valid_fields, StringView type_name); - - public: - template - void required_object_field( - StringView type, const Object& obj, StringView key, Type& place, Deserializer&& visitor) - { - if (!internal_field(obj, key, place, visitor)) - { - this->add_missing_field_error(type, key, visitor.type_name()); - } - } - - // returns whether key \in obj - template - bool optional_object_field(const Object& obj, StringView key, Type& place, Deserializer&& visitor) - { - return internal_field(obj, key, place, visitor); - } - - template - Optional visit_value(const Value& value, IDeserializer& visitor) - { - return internal_visit(value, visitor); - } - template - Optional visit_value(const Value& value, IDeserializer&& visitor) - { - return visit_value(value, visitor); - } - - template - Optional visit_value(const Array& value, IDeserializer& visitor) - { - return visitor.visit_array(*this, value); - } - template - Optional visit_value(const Array& value, IDeserializer&& visitor) - { - return visit_value(value, visitor); - } - - template - Optional visit_value(const Object& value, IDeserializer& visitor) - { - check_for_unexpected_fields(value, visitor.valid_fields(), visitor.type_name()); - return visitor.visit_object(*this, value); - } - template - Optional visit_value(const Object& value, IDeserializer&& visitor) - { - return visit_value(value, visitor); - } - - template - Optional visit_map_field(StringView key, const Value& value, IDeserializer& visitor) - { - m_path.push_back(key); - auto res = internal_visit(value, visitor); - m_path.pop_back(); - return res; - } - template - Optional visit_map_field(StringView key, const Value& value, IDeserializer&& visitor) - { - return visit_map_field(key, value, visitor); - } - - template - Optional> array_elements(const Array& arr, IDeserializer& visitor) - { - std::vector result; - m_path.emplace_back(); - for (size_t i = 0; i < arr.size(); ++i) - { - m_path.back().index = static_cast(i); - auto opt = internal_visit(arr[i], visitor); - if (auto p = opt.get()) - { - result.push_back(std::move(*p)); - } - else - { - this->add_expected_type_error(visitor.type_name()); - for (++i; i < arr.size(); ++i) - { - m_path.back().index = static_cast(i); - auto opt2 = internal_visit(arr[i], visitor); - if (!opt2) this->add_expected_type_error(visitor.type_name()); - } - } - } - m_path.pop_back(); - return std::move(result); - } - template - Optional> array_elements(const Array& arr, IDeserializer&& visitor) - { - return array_elements(arr, visitor); - } - }; - - struct StringDeserializer final : IDeserializer - { - virtual StringView type_name() const override { return type_name_; } - virtual Optional visit_string(Reader&, StringView sv) override { return sv.to_string(); } - - explicit StringDeserializer(StringView type_name_) : type_name_(type_name_) { } - - private: - StringView type_name_; - }; - - struct PathDeserializer final : IDeserializer - { - virtual StringView type_name() const override { return "a path"; } - virtual Optional visit_string(Reader&, StringView sv) override { return fs::u8path(sv); } - }; - - struct NaturalNumberDeserializer final : IDeserializer - { - virtual StringView type_name() const override { return "a natural number"; } - - virtual Optional visit_integer(Reader&, int64_t value) override - { - if (value > std::numeric_limits::max() || value < 0) - { - return nullopt; - } - return static_cast(value); - } - }; - - struct BooleanDeserializer final : IDeserializer - { - virtual StringView type_name() const override { return "a boolean"; } - - virtual Optional visit_boolean(Reader&, bool b) override { return b; } - }; - - enum class AllowEmpty : bool - { - No, - Yes, - }; - - template - struct ArrayDeserializer final : IDeserializer> - { - using typename IDeserializer>::type; - - virtual StringView type_name() const override { return type_name_; } - - ArrayDeserializer(StringView type_name_, AllowEmpty allow_empty, Underlying&& t = {}) - : type_name_(type_name_), underlying_visitor_(static_cast(t)), allow_empty_(allow_empty) - { - } - - virtual Optional visit_array(Reader& r, const Array& arr) override - { - if (allow_empty_ == AllowEmpty::No && arr.size() == 0) - { - return nullopt; - } - return r.array_elements(arr, underlying_visitor_); - } - - private: - StringView type_name_; - Underlying underlying_visitor_; - AllowEmpty allow_empty_; - }; - - struct ParagraphDeserializer final : IDeserializer> - { - virtual StringView type_name() const override { return "a string or array of strings"; } - - virtual Optional> visit_string(Reader&, StringView sv) override - { - std::vector out; - out.push_back(sv.to_string()); - return out; - } - - virtual Optional> visit_array(Reader& r, const Array& arr) override - { - return r.array_elements(arr, StringDeserializer{"a string"}); - } - }; - - struct IdentifierDeserializer final : Json::IDeserializer - { - virtual StringView type_name() const override { return "an identifier"; } - - // [a-z0-9]+(-[a-z0-9]+)*, plus not any of {prn, aux, nul, con, lpt[1-9], com[1-9], core, default} - static bool is_ident(StringView sv); - - virtual Optional visit_string(Json::Reader&, StringView sv) override - { - if (is_ident(sv)) - { - return sv.to_string(); - } - else - { - return nullopt; - } - } - }; - - struct PackageNameDeserializer final : Json::IDeserializer - { - virtual StringView type_name() const override { return "a package name"; } - - static bool is_package_name(StringView sv) - { - if (sv.size() == 0) - { - return false; - } - - for (const auto& ident : Strings::split(sv, '.')) - { - if (!IdentifierDeserializer::is_ident(ident)) - { - return false; - } - } - - return true; - } - - virtual Optional visit_string(Json::Reader&, StringView sv) override - { - if (!is_package_name(sv)) - { - return nullopt; - } - return sv.to_string(); - } - }; - ExpectedT, std::unique_ptr> parse_file( const Files::Filesystem&, const fs::path&, std::error_code& ec) noexcept; ExpectedT, std::unique_ptr> parse( @@ -671,5 +297,4 @@ namespace vcpkg::Json std::string stringify(const Value&, JsonStyle style); std::string stringify(const Object&, JsonStyle style); std::string stringify(const Array&, JsonStyle style); - } diff --git a/include/vcpkg/base/jsonreader.h b/include/vcpkg/base/jsonreader.h new file mode 100644 index 00000000000000..6518501a240e73 --- /dev/null +++ b/include/vcpkg/base/jsonreader.h @@ -0,0 +1,347 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace vcpkg::Json +{ + struct Reader; + + template + struct IDeserializer + { + using type = Type; + virtual StringView type_name() const = 0; + + private: + friend struct Reader; + Optional visit(Reader&, const Value&); + Optional visit(Reader&, const Object&); + + protected: + virtual Optional visit_null(Reader&); + virtual Optional visit_boolean(Reader&, bool); + virtual Optional visit_integer(Reader& r, int64_t i); + virtual Optional visit_number(Reader&, double); + virtual Optional visit_string(Reader&, StringView); + virtual Optional visit_array(Reader&, const Array&); + virtual Optional visit_object(Reader&, const Object&); + virtual View valid_fields() const; + + IDeserializer() = default; + IDeserializer(const IDeserializer&) = default; + IDeserializer& operator=(const IDeserializer&) = default; + IDeserializer(IDeserializer&&) = default; + IDeserializer& operator=(IDeserializer&&) = default; + virtual ~IDeserializer() = default; + }; + + struct Reader + { + const std::vector& errors() const { return m_errors; } + std::vector& errors() { return m_errors; } + + void add_missing_field_error(StringView type, StringView key, StringView key_type); + void add_expected_type_error(StringView expected_type); + void add_extra_field_error(StringView type, StringView fields, StringView suggestion = {}); + template + void add_generic_error(StringView type, Args&&... args) + { + m_errors.push_back(Strings::concat(path(), " (", type, "): ", args...)); + } + + std::string path() const noexcept; + + private: + template + friend struct IDeserializer; + + std::vector m_errors; + struct Path + { + constexpr Path() = default; + constexpr Path(int64_t i) : index(i) { } + constexpr Path(StringView f) : field(f) { } + + int64_t index = -1; + StringView field; + }; + std::vector m_path; + + // checks that an object doesn't contain any fields which both: + // * don't start with a `$` + // * are not in `valid_fields` + // if known_fields.empty(), then it's treated as if all field names are valid + void check_for_unexpected_fields(const Object& obj, View valid_fields, StringView type_name); + + public: + template + void required_object_field( + StringView type, const Object& obj, StringView key, Type& place, IDeserializer& visitor) + { + if (auto value = obj.get(key)) + { + visit_in_key(*value, key, place, visitor); + } + else + { + this->add_missing_field_error(type, key, visitor.type_name()); + } + } + + // value should be the value at key of the currently visited object + template + void visit_in_key(const Value& value, StringView key, Type& place, IDeserializer& visitor) + { + m_path.push_back(key); + auto opt = visitor.visit(*this, value); + + if (auto p_opt = opt.get()) + { + place = std::move(*p_opt); + } + else + { + add_expected_type_error(visitor.type_name()); + } + m_path.pop_back(); + } + + // returns whether key \in obj + template + bool optional_object_field(const Object& obj, StringView key, Type& place, IDeserializer& visitor) + { + if (auto value = obj.get(key)) + { + visit_in_key(*value, key, place, visitor); + return true; + } + else + { + return false; + } + } + + template + Optional visit(const Value& value, IDeserializer& visitor) + { + return visitor.visit(*this, value); + } + template + Optional visit(const Object& value, IDeserializer& visitor) + { + return visitor.visit(*this, value); + } + + template + Optional> array_elements(const Array& arr, IDeserializer& visitor) + { + std::vector result; + m_path.emplace_back(); + for (size_t i = 0; i < arr.size(); ++i) + { + m_path.back().index = static_cast(i); + auto opt = visitor.visit(*this, arr[i]); + if (auto p = opt.get()) + { + result.push_back(std::move(*p)); + } + else + { + this->add_expected_type_error(visitor.type_name()); + for (++i; i < arr.size(); ++i) + { + m_path.back().index = static_cast(i); + auto opt2 = visitor.visit(*this, arr[i]); + if (!opt2) this->add_expected_type_error(visitor.type_name()); + } + } + } + m_path.pop_back(); + return std::move(result); + } + }; + + VCPKG_MSVC_WARNING(push); + VCPKG_MSVC_WARNING(disable : 4505); + + template + Optional IDeserializer::visit(Reader& r, const Value& value) + { + switch (value.kind()) + { + case ValueKind::Null: return visit_null(r); + case ValueKind::Boolean: return visit_boolean(r, value.boolean()); + case ValueKind::Integer: return visit_integer(r, value.integer()); + case ValueKind::Number: return visit_number(r, value.number()); + case ValueKind::String: return visit_string(r, value.string()); + case ValueKind::Array: return visit_array(r, value.array()); + case ValueKind::Object: return visit(r, value.object()); // Call `visit` to get unexpected fields checking + default: vcpkg::Checks::unreachable(VCPKG_LINE_INFO); + } + } + + template + Optional IDeserializer::visit(Reader& r, const Object& obj) + { + r.check_for_unexpected_fields(obj, valid_fields(), type_name()); + return visit_object(r, obj); + } + + template + View IDeserializer::valid_fields() const + { + return {}; + } + + template + Optional IDeserializer::visit_null(Reader&) + { + return nullopt; + } + template + Optional IDeserializer::visit_boolean(Reader&, bool) + { + return nullopt; + } + template + Optional IDeserializer::visit_integer(Reader& r, int64_t i) + { + return this->visit_number(r, static_cast(i)); + } + template + Optional IDeserializer::visit_number(Reader&, double) + { + return nullopt; + } + template + Optional IDeserializer::visit_string(Reader&, StringView) + { + return nullopt; + } + template + Optional IDeserializer::visit_array(Reader&, const Array&) + { + return nullopt; + } + template + Optional IDeserializer::visit_object(Reader&, const Object&) + { + return nullopt; + } + + VCPKG_MSVC_WARNING(pop); + + struct StringDeserializer final : IDeserializer + { + virtual StringView type_name() const override { return type_name_; } + virtual Optional visit_string(Reader&, StringView sv) override { return sv.to_string(); } + + explicit StringDeserializer(StringLiteral type_name_) : type_name_(type_name_) { } + + private: + StringLiteral type_name_; + }; + + struct PathDeserializer final : IDeserializer + { + virtual StringView type_name() const override { return "a path"; } + virtual Optional visit_string(Reader&, StringView sv) override { return fs::u8path(sv); } + + static PathDeserializer instance; + }; + + struct NaturalNumberDeserializer final : IDeserializer + { + virtual StringView type_name() const override { return "a natural number"; } + + virtual Optional visit_integer(Reader&, int64_t value) override + { + if (value > std::numeric_limits::max() || value < 0) + { + return nullopt; + } + return static_cast(value); + } + + static NaturalNumberDeserializer instance; + }; + + struct BooleanDeserializer final : IDeserializer + { + virtual StringView type_name() const override { return "a boolean"; } + + virtual Optional visit_boolean(Reader&, bool b) override { return b; } + + static BooleanDeserializer instance; + }; + + template + struct ArrayDeserializer final : IDeserializer> + { + using type = std::vector; + + virtual StringView type_name() const override { return m_type_name; } + + ArrayDeserializer(StringLiteral type_name_, Underlying&& t = {}) + : m_type_name(type_name_), m_underlying_visitor(static_cast(t)) + { + } + + virtual Optional visit_array(Reader& r, const Array& arr) override + { + return r.array_elements(arr, m_underlying_visitor); + } + + private: + StringLiteral m_type_name; + Underlying m_underlying_visitor; + }; + + struct ParagraphDeserializer final : IDeserializer> + { + virtual StringView type_name() const override { return "a string or array of strings"; } + + virtual Optional> visit_string(Reader&, StringView sv) override; + virtual Optional> visit_array(Reader& r, const Array& arr) override; + + static ParagraphDeserializer instance; + }; + + struct IdentifierDeserializer final : Json::IDeserializer + { + virtual StringView type_name() const override { return "an identifier"; } + + // [a-z0-9]+(-[a-z0-9]+)*, plus not any of {prn, aux, nul, con, lpt[1-9], com[1-9], core, default} + static bool is_ident(StringView sv); + + virtual Optional visit_string(Json::Reader&, StringView sv) override; + + static IdentifierDeserializer instance; + }; + + struct IdentifierArrayDeserializer final : Json::IDeserializer> + { + virtual StringView type_name() const override { return "an array of identifiers"; } + + virtual Optional> visit_array(Reader& r, const Array& arr) override; + + static IdentifierArrayDeserializer instance; + }; + + struct PackageNameDeserializer final : Json::IDeserializer + { + virtual StringView type_name() const override { return "a package name"; } + + static bool is_package_name(StringView sv); + + virtual Optional visit_string(Json::Reader&, StringView sv) override; + + static PackageNameDeserializer instance; + }; +} diff --git a/include/vcpkg/base/span.h b/include/vcpkg/base/span.h index a66205332e33e9..91d94ec473182e 100644 --- a/include/vcpkg/base/span.h +++ b/include/vcpkg/base/span.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include diff --git a/include/vcpkg/base/strings.h b/include/vcpkg/base/strings.h index 0962d6c1e0cb14..5113ff2fc3bf4b 100644 --- a/include/vcpkg/base/strings.h +++ b/include/vcpkg/base/strings.h @@ -94,6 +94,13 @@ namespace vcpkg::Strings return ret; } + template + [[nodiscard]] std::string concat(std::string&& first, const Args&... args) + { + append(first, args...); + return std::move(first); + } + template std::string concat_or_view(const Args&... args) { @@ -288,4 +295,8 @@ namespace vcpkg::Strings // base 32 encoding, following IETC RFC 4648 std::string b32_encode(std::uint64_t x) noexcept; + + // Implements https://en.wikipedia.org/wiki/Levenshtein_distance with a "give-up" clause for large strings + // Guarantees 0 for equal strings and nonzero for inequal strings. + size_t byte_edit_distance(StringView a, StringView b); } diff --git a/include/vcpkg/base/view.h b/include/vcpkg/base/view.h index 491e8351b25e21..12908c4a5019ad 100644 --- a/include/vcpkg/base/view.h +++ b/include/vcpkg/base/view.h @@ -1,9 +1,3 @@ #pragma once #include - -namespace vcpkg -{ - template - using View = Span; -} diff --git a/include/vcpkg/configuration.h b/include/vcpkg/configuration.h index a4ed38223380aa..4cba88fe5e06e5 100644 --- a/include/vcpkg/configuration.h +++ b/include/vcpkg/configuration.h @@ -1,9 +1,6 @@ #pragma once #include -#include - -#include #include @@ -16,26 +13,4 @@ namespace vcpkg // taken care of in RegistrySet. RegistrySet registry_set; }; - - struct ConfigurationDeserializer final : Json::IDeserializer - { - virtual StringView type_name() const override { return "a configuration object"; } - - constexpr static StringLiteral DEFAULT_REGISTRY = "default-registry"; - constexpr static StringLiteral REGISTRIES = "registries"; - virtual Span valid_fields() const override - { - constexpr static StringView t[] = {DEFAULT_REGISTRY, REGISTRIES}; - return t; - } - - virtual Optional visit_object(Json::Reader& r, const Json::Object& obj) override; - - ConfigurationDeserializer(const VcpkgCmdArguments& args); - - private: - bool print_json; - - bool registries_enabled; - }; } diff --git a/include/vcpkg/configurationdeserializer.h b/include/vcpkg/configurationdeserializer.h new file mode 100644 index 00000000000000..83d3d0869f8ce8 --- /dev/null +++ b/include/vcpkg/configurationdeserializer.h @@ -0,0 +1,66 @@ +#pragma once + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace vcpkg +{ + struct RegistryImplDeserializer : Json::IDeserializer> + { + constexpr static StringLiteral KIND = "kind"; + constexpr static StringLiteral PATH = "path"; + + constexpr static StringLiteral KIND_BUILTIN = "builtin"; + constexpr static StringLiteral KIND_DIRECTORY = "directory"; + + virtual StringView type_name() const override; + virtual View valid_fields() const override; + + virtual Optional> visit_null(Json::Reader&) override; + virtual Optional> visit_object(Json::Reader&, const Json::Object&) override; + + static RegistryImplDeserializer instance; + }; + + struct RegistryDeserializer final : Json::IDeserializer + { + constexpr static StringLiteral PACKAGES = "packages"; + + virtual StringView type_name() const override; + virtual View valid_fields() const override; + + virtual Optional visit_object(Json::Reader&, const Json::Object&) override; + }; + + struct ConfigurationDeserializer final : Json::IDeserializer + { + virtual StringView type_name() const override { return "a configuration object"; } + + constexpr static StringLiteral DEFAULT_REGISTRY = "default-registry"; + constexpr static StringLiteral REGISTRIES = "registries"; + virtual View valid_fields() const override + { + constexpr static StringView t[] = {DEFAULT_REGISTRY, REGISTRIES}; + return t; + } + + virtual Optional visit_object(Json::Reader& r, const Json::Object& obj) override; + + ConfigurationDeserializer(const VcpkgCmdArguments& args); + + private: + bool print_json; + + bool registries_enabled; + }; +} diff --git a/include/vcpkg/registries.h b/include/vcpkg/registries.h index 9119ba17bad005..812bce86b71f2d 100644 --- a/include/vcpkg/registries.h +++ b/include/vcpkg/registries.h @@ -1,11 +1,10 @@ #pragma once -#include - #include #include -#include +#include +#include #include #include @@ -29,7 +28,7 @@ namespace vcpkg Registry(std::vector&&, std::nullptr_t) = delete; // always ordered lexicographically - Span packages() const { return packages_; } + View packages() const { return packages_; } const RegistryImpl& implementation() const { return *implementation_; } static std::unique_ptr builtin_registry(); @@ -39,31 +38,6 @@ namespace vcpkg std::unique_ptr implementation_; }; - struct RegistryImplDeserializer : Json::IDeserializer> - { - constexpr static StringLiteral KIND = "kind"; - constexpr static StringLiteral PATH = "path"; - - constexpr static StringLiteral KIND_BUILTIN = "builtin"; - constexpr static StringLiteral KIND_DIRECTORY = "directory"; - - virtual StringView type_name() const override; - virtual Span valid_fields() const override; - - virtual Optional> visit_null(Json::Reader&) override; - virtual Optional> visit_object(Json::Reader&, const Json::Object&) override; - }; - - struct RegistryDeserializer final : Json::IDeserializer - { - constexpr static StringLiteral PACKAGES = "packages"; - - virtual StringView type_name() const override; - virtual Span valid_fields() const override; - - virtual Optional visit_object(Json::Reader&, const Json::Object&) override; - }; - // this type implements the registry fall back logic from the registries RFC: // A port name maps to one of the non-default registries if that registry declares // that it is the registry for that port name, else it maps to the default registry @@ -78,7 +52,7 @@ namespace vcpkg // Returns the null pointer if there is no registry set up for that name const RegistryImpl* registry_for_port(StringView port_name) const; - Span registries() const { return registries_; } + View registries() const { return registries_; } const RegistryImpl* default_registry() const { return default_registry_.get(); } diff --git a/src/vcpkg-test/manifests.cpp b/src/vcpkg-test/manifests.cpp index 0d5c9782a9e799..77f4eb440a675d 100644 --- a/src/vcpkg-test/manifests.cpp +++ b/src/vcpkg-test/manifests.cpp @@ -248,6 +248,17 @@ TEST_CASE ("SourceParagraph manifest empty supports", "[manifests]") REQUIRE_FALSE(m_pgh.has_value()); } +TEST_CASE ("SourceParagraph manifest non-string supports", "[manifests]") +{ + auto m_pgh = test_parse_manifest(R"json({ + "name": "a", + "version-string": "1.0", + "supports": true + })json", + true); + REQUIRE_FALSE(m_pgh.has_value()); +} + TEST_CASE ("Serialize all the ports", "[manifests]") { std::vector args_list = {"format-manifest"}; diff --git a/src/vcpkg-test/strings.cpp b/src/vcpkg-test/strings.cpp index cc45365e691a73..ecf0524bc73530 100644 --- a/src/vcpkg-test/strings.cpp +++ b/src/vcpkg-test/strings.cpp @@ -56,3 +56,18 @@ TEST_CASE ("find_first_of", "[strings]") REQUIRE(find_first_of("abcdefg", "bg") == std::string("bcdefg")); REQUIRE(find_first_of("abcdefg", "gb") == std::string("bcdefg")); } + +TEST_CASE ("edit distance", "[strings]") +{ + using vcpkg::Strings::byte_edit_distance; + REQUIRE(byte_edit_distance("", "") == 0); + REQUIRE(byte_edit_distance("a", "a") == 0); + REQUIRE(byte_edit_distance("abcd", "abcd") == 0); + REQUIRE(byte_edit_distance("aaa", "aa") == 1); + REQUIRE(byte_edit_distance("aa", "aaa") == 1); + REQUIRE(byte_edit_distance("abcdef", "bcdefa") == 2); + REQUIRE(byte_edit_distance("hello", "world") == 4); + REQUIRE(byte_edit_distance("CAPITAL", "capital") == 7); + REQUIRE(byte_edit_distance("", "hello") == 5); + REQUIRE(byte_edit_distance("world", "") == 5); +} diff --git a/src/vcpkg/base/json.cpp b/src/vcpkg/base/json.cpp index 495a36e32e05a4..0355b0c9417e83 100644 --- a/src/vcpkg/base/json.cpp +++ b/src/vcpkg/base/json.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -986,6 +987,14 @@ namespace vcpkg::Json }; } + NaturalNumberDeserializer NaturalNumberDeserializer::instance; + BooleanDeserializer BooleanDeserializer::instance; + ParagraphDeserializer ParagraphDeserializer::instance; + IdentifierDeserializer IdentifierDeserializer::instance; + IdentifierArrayDeserializer IdentifierArrayDeserializer::instance; + PackageNameDeserializer PackageNameDeserializer::instance; + PathDeserializer PathDeserializer::instance; + bool IdentifierDeserializer::is_ident(StringView sv) { static const std::regex BASIC_IDENTIFIER = std::regex(R"([a-z0-9]+(-[a-z0-9]+)*)"); @@ -1282,9 +1291,27 @@ namespace vcpkg::Json return res; } - void Reader::check_for_unexpected_fields(const Object& obj, - Span valid_fields, - StringView type_name) + void Reader::add_missing_field_error(StringView type, StringView key, StringView key_type) + { + add_generic_error(type, "missing required field '", key, "' (", key_type, ")"); + } + void Reader::add_expected_type_error(StringView expected_type) + { + m_errors.push_back(Strings::concat(path(), ": mismatched type: expected ", expected_type)); + } + void Reader::add_extra_field_error(StringView type, StringView field, StringView suggestion) + { + if (suggestion.size() > 0) + { + add_generic_error(type, "unexpected field '", field, "\', did you mean \'", suggestion, "\'?"); + } + else + { + add_generic_error(type, "unexpected field '", field, '\''); + } + } + + void Reader::check_for_unexpected_fields(const Object& obj, View valid_fields, StringView type_name) { if (valid_fields.size() == 0) { @@ -1292,9 +1319,20 @@ namespace vcpkg::Json } auto extra_fields = invalid_json_fields(obj, valid_fields); - if (!extra_fields.empty()) + for (auto&& f : extra_fields) { - add_extra_fields_error(type_name.to_string(), std::move(extra_fields)); + auto best_it = valid_fields.begin(); + auto best_value = Strings::byte_edit_distance(f, *best_it); + for (auto i = best_it + 1; i != valid_fields.end(); ++i) + { + auto v = Strings::byte_edit_distance(f, *i); + if (v < best_value) + { + best_value = v; + best_it = i; + } + } + add_extra_field_error(type_name.to_string(), f, *best_it); } } @@ -1311,4 +1349,57 @@ namespace vcpkg::Json return p; } + Optional> ParagraphDeserializer::visit_string(Reader&, StringView sv) + { + std::vector out; + out.push_back(sv.to_string()); + return out; + } + + Optional> ParagraphDeserializer::visit_array(Reader& r, const Array& arr) + { + static StringDeserializer d{"a string"}; + return r.array_elements(arr, d); + } + + Optional IdentifierDeserializer::visit_string(Json::Reader& r, StringView sv) + { + if (!is_ident(sv)) + { + r.add_generic_error(type_name(), "must be lowercase alphanumeric+hyphens and not reserved"); + } + return sv.to_string(); + } + + Optional> IdentifierArrayDeserializer::visit_array(Reader& r, const Array& arr) + { + return r.array_elements(arr, IdentifierDeserializer::instance); + } + + bool PackageNameDeserializer::is_package_name(StringView sv) + { + if (sv.size() == 0) + { + return false; + } + + for (const auto& ident : Strings::split(sv, '.')) + { + if (!IdentifierDeserializer::is_ident(ident)) + { + return false; + } + } + + return true; + } + + Optional PackageNameDeserializer::visit_string(Json::Reader&, StringView sv) + { + if (!is_package_name(sv)) + { + return nullopt; + } + return sv.to_string(); + } } diff --git a/src/vcpkg/base/strings.cpp b/src/vcpkg/base/strings.cpp index 49aaa30bdf09ac..a59cdaceb7cca0 100644 --- a/src/vcpkg/base/strings.cpp +++ b/src/vcpkg/base/strings.cpp @@ -267,6 +267,55 @@ bool Strings::contains(StringView haystack, StringView needle) return Strings::search(haystack, needle) != haystack.end(); } +size_t Strings::byte_edit_distance(StringView a, StringView b) +{ + static constexpr size_t max_string_size = 100; + // For large strings, give up early to avoid performance problems + if (a.size() > max_string_size || b.size() > max_string_size) + { + if (a == b) + return 0; + else + return std::max(a.size(), b.size()); + } + if (a.size() == 0 || b.size() == 0) return std::max(a.size(), b.size()); + + auto pa = a.data(); + auto pb = b.data(); + size_t sa = a.size(); + size_t sb = b.size(); + + // Levenshtein distance (https://en.wikipedia.org/wiki/Levenshtein_distance) + // The first row of the edit distance matrix has been omitted because it's trivial (counting from 0) + // Because each subsequent row only depends on the row above, we never need to store the entire matrix + char d[max_string_size]; + + // Useful invariants: + // `sa` is sizeof `pa` using iterator `ia` + // `sb` is sizeof `pb` using iterator `ib` + // `sa` and `sb` are in (0, `max_string_size`] + + // To avoid dealing with edge effects, `ia` == 0 and `ib` == 0 have been unrolled. + // Comparisons are used as the cost for the diagonal action (substitute/leave unchanged) + d[0] = pa[0] != pb[0]; + for (size_t ia = 1; ia < sa; ++ia) + d[ia] = std::min(d[ia - 1] + 1, static_cast(ia + (pa[ia] != pb[0]))); + + for (size_t ib = 1; ib < sb; ++ib) + { + // The diagonal information (d[ib-1][ia-1]) is used to compute substitution cost and so must be preserved + char diag = d[0]; + d[0] = std::min(d[0] + 1, static_cast(ib + (pa[0] != pb[ib]))); + for (size_t ia = 1; ia < sa; ++ia) + { + auto subst_or_add = std::min(d[ia - 1] + 1, static_cast(diag + (pa[ia] != pb[ib]))); + diag = d[ia]; + d[ia] = std::min(d[ia] + 1, subst_or_add); + } + } + return d[sa - 1]; +} + namespace vcpkg::Strings { namespace diff --git a/src/vcpkg/configuration.cpp b/src/vcpkg/configuration.cpp index 9363abe8086552..f062af51d37d20 100644 --- a/src/vcpkg/configuration.cpp +++ b/src/vcpkg/configuration.cpp @@ -1,6 +1,8 @@ +#include #include #include +#include #include namespace vcpkg @@ -13,7 +15,7 @@ namespace vcpkg { std::unique_ptr default_registry; - if (r.optional_object_field(obj, DEFAULT_REGISTRY, default_registry, RegistryImplDeserializer{})) + if (r.optional_object_field(obj, DEFAULT_REGISTRY, default_registry, RegistryImplDeserializer::instance)) { if (!registries_enabled) { @@ -26,12 +28,10 @@ namespace vcpkg } } + static Json::ArrayDeserializer array_of_registries{"an array of registries"}; + std::vector regs; - r.optional_object_field( - obj, - REGISTRIES, - regs, - Json::ArrayDeserializer{"an array of registries", Json::AllowEmpty::Yes}); + r.optional_object_field(obj, REGISTRIES, regs, array_of_registries); if (!regs.empty() && !registries_enabled) { diff --git a/src/vcpkg/install.cpp b/src/vcpkg/install.cpp index 99d17f45939ad2..a83c6c9765712b 100644 --- a/src/vcpkg/install.cpp +++ b/src/vcpkg/install.cpp @@ -794,9 +794,8 @@ namespace vcpkg::Install if (!maybe_manifest_scf) { print_error_message(maybe_manifest_scf.error()); - System::print2( - "See https://github.com/Microsoft/vcpkg/tree/master/docs/specifications/manifests.md for " - "more information.\n"); + System::print2("See https://github.com/Microsoft/vcpkg/tree/master/docs/users/manifests.md for " + "more information.\n"); Checks::exit_fail(VCPKG_LINE_INFO); } auto& manifest_scf = *maybe_manifest_scf.value_or_exit(VCPKG_LINE_INFO); diff --git a/src/vcpkg/platform-expression.cpp b/src/vcpkg/platform-expression.cpp index 1d38dbc05a356d..9132d4be47e377 100644 --- a/src/vcpkg/platform-expression.cpp +++ b/src/vcpkg/platform-expression.cpp @@ -461,7 +461,7 @@ namespace vcpkg::PlatformExpression ExpectedS parse_platform_expression(StringView expression, MultipleBinaryOperators multiple_binary_operators) { - auto parser = ExpressionParser(expression, multiple_binary_operators); + ExpressionParser parser(expression, multiple_binary_operators); auto res = parser.parse(); if (auto p = parser.extract_error()) diff --git a/src/vcpkg/registries.cpp b/src/vcpkg/registries.cpp index ecc63ab3dee200..f668630683139a 100644 --- a/src/vcpkg/registries.cpp +++ b/src/vcpkg/registries.cpp @@ -1,5 +1,7 @@ #include +#include +#include #include #include @@ -40,7 +42,7 @@ namespace vcpkg constexpr StringLiteral RegistryImplDeserializer::KIND_BUILTIN; constexpr StringLiteral RegistryImplDeserializer::KIND_DIRECTORY; - Span RegistryImplDeserializer::valid_fields() const + View RegistryImplDeserializer::valid_fields() const { static const StringView t[] = {KIND, PATH}; return t; @@ -51,22 +53,22 @@ namespace vcpkg Optional> RegistryImplDeserializer::visit_object(Json::Reader& r, const Json::Object& obj) { + static Json::StringDeserializer kind_deserializer{"a registry implementation kind"}; std::string kind; - r.required_object_field( - type_name(), obj, KIND, kind, Json::StringDeserializer{"a registry implementation kind"}); + r.required_object_field(type_name(), obj, KIND, kind, kind_deserializer); if (kind == KIND_BUILTIN) { if (obj.contains(PATH)) { - r.add_extra_fields_error("a builtin registry", {PATH}); + r.add_extra_field_error("a builtin registry", PATH); } return static_cast>(std::make_unique()); } else if (kind == KIND_DIRECTORY) { fs::path path; - r.required_object_field("a directory registry", obj, PATH, path, Json::PathDeserializer{}); + r.required_object_field("a directory registry", obj, PATH, path, Json::PathDeserializer::instance); return static_cast>(std::make_unique(std::move(path))); } @@ -75,12 +77,13 @@ namespace vcpkg return nullopt; } } + RegistryImplDeserializer RegistryImplDeserializer::instance; StringView RegistryDeserializer::type_name() const { return "a registry"; } constexpr StringLiteral RegistryDeserializer::PACKAGES; - Span RegistryDeserializer::valid_fields() const + View RegistryDeserializer::valid_fields() const { static const StringView t[] = { RegistryImplDeserializer::KIND, @@ -99,13 +102,11 @@ namespace vcpkg return nullopt; } + static Json::ArrayDeserializer package_names_deserializer{ + "an array of package names"}; + std::vector packages; - r.required_object_field( - type_name(), - obj, - PACKAGES, - packages, - Json::ArrayDeserializer{"an array of registries", Json::AllowEmpty::Yes}); + r.required_object_field(type_name(), obj, PACKAGES, packages, package_names_deserializer); return Registry{std::move(packages), std::move(impl).value_or_exit(VCPKG_LINE_INFO)}; } diff --git a/src/vcpkg/sourceparagraph.cpp b/src/vcpkg/sourceparagraph.cpp index 9300c8e6c0818f..a01f05b28ab22f 100644 --- a/src/vcpkg/sourceparagraph.cpp +++ b/src/vcpkg/sourceparagraph.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -361,7 +362,7 @@ namespace vcpkg { virtual StringView type_name() const override { return "a platform expression"; } - virtual Optional visit_string(Json::Reader&, StringView sv) override + virtual Optional visit_string(Json::Reader& r, StringView sv) override { auto opt = PlatformExpression::parse_platform_expression(sv, PlatformExpression::MultipleBinaryOperators::Deny); @@ -371,11 +372,14 @@ namespace vcpkg } else { - Debug::print("Failed to parse platform expression: ", opt.error(), "\n"); - return nullopt; + r.add_generic_error(type_name(), opt.error()); + return PlatformExpression::Expr::Empty(); } } + + static PlatformExprDeserializer instance; }; + PlatformExprDeserializer PlatformExprDeserializer::instance; struct DependencyDeserializer : Json::IDeserializer { @@ -398,11 +402,12 @@ namespace vcpkg return t; } - virtual Optional visit_string(Json::Reader&, StringView sv) override + virtual Optional visit_string(Json::Reader& r, StringView sv) override { if (!Json::PackageNameDeserializer::is_package_name(sv)) { - return nullopt; + r.add_generic_error(type_name(), + "must be lowercase alphanumeric+hyphens, split with periods, and not reserved"); } Dependency dep; @@ -422,25 +427,39 @@ namespace vcpkg } } - r.required_object_field(type_name(), obj, NAME, dep.name, Json::PackageNameDeserializer{}); - r.optional_object_field(obj, - FEATURES, - dep.features, - Json::ArrayDeserializer{"an array of identifiers", - Json::AllowEmpty::Yes}); + static Json::ArrayDeserializer arr_id_d{"an array of identifiers"}; + + r.required_object_field(type_name(), obj, NAME, dep.name, Json::PackageNameDeserializer::instance); + r.optional_object_field(obj, FEATURES, dep.features, arr_id_d); bool default_features = true; - r.optional_object_field(obj, DEFAULT_FEATURES, default_features, Json::BooleanDeserializer{}); + r.optional_object_field(obj, DEFAULT_FEATURES, default_features, Json::BooleanDeserializer::instance); if (!default_features) { dep.features.push_back("core"); } - r.optional_object_field(obj, PLATFORM, dep.platform, PlatformExprDeserializer{}); + r.optional_object_field(obj, PLATFORM, dep.platform, PlatformExprDeserializer::instance); return dep; } + + static DependencyDeserializer instance; }; + DependencyDeserializer DependencyDeserializer::instance; + + struct DependencyArrayDeserializer final : Json::IDeserializer> + { + virtual StringView type_name() const override { return "an array of dependencies"; } + + virtual Optional> visit_array(Json::Reader& r, const Json::Array& arr) override + { + return r.array_elements(arr, DependencyDeserializer::instance); + } + + static DependencyArrayDeserializer instance; + }; + DependencyArrayDeserializer DependencyArrayDeserializer::instance; constexpr StringLiteral DependencyDeserializer::NAME; constexpr StringLiteral DependencyDeserializer::FEATURES; @@ -470,8 +489,6 @@ namespace vcpkg const Json::Object& obj) override { auto feature = std::make_unique(); - feature->name = std::move(name); - for (const auto& el : obj) { if (Strings::starts_with(el.first, "$")) @@ -480,21 +497,18 @@ namespace vcpkg } } - r.required_object_field("a feature", obj, DESCRIPTION, feature->description, Json::ParagraphDeserializer{}); - r.optional_object_field( - obj, - DEPENDENCIES, - feature->dependencies, - Json::ArrayDeserializer{"an array of dependencies", Json::AllowEmpty::Yes}); + r.required_object_field( + type_name(), obj, DESCRIPTION, feature->description, Json::ParagraphDeserializer::instance); + r.optional_object_field(obj, DEPENDENCIES, feature->dependencies, DependencyArrayDeserializer::instance); return std::move(feature); } - - FeatureDeserializer() = default; - FeatureDeserializer(std::string&& s) : name(std::move(s)) { } - - std::string name; + static FeatureDeserializer instance; }; + FeatureDeserializer FeatureDeserializer::instance; + constexpr StringLiteral FeatureDeserializer::NAME; + constexpr StringLiteral FeatureDeserializer::DESCRIPTION; + constexpr StringLiteral FeatureDeserializer::DEPENDENCIES; struct ArrayFeatureDeserializer : Json::IDeserializer> { @@ -514,27 +528,31 @@ namespace vcpkg const Json::Object& obj) override { std::string name; - r.required_object_field(type_name(), obj, FeatureDeserializer::NAME, name, Json::IdentifierDeserializer{}); - return FeatureDeserializer{std::move(name)}.visit_object(r, obj); + r.required_object_field( + type_name(), obj, FeatureDeserializer::NAME, name, Json::IdentifierDeserializer::instance); + auto opt = FeatureDeserializer::instance.visit_object(r, obj); + if (auto p = opt.get()) + { + p->get()->name = std::move(name); + } + return opt; } - }; - constexpr StringLiteral FeatureDeserializer::NAME; - constexpr StringLiteral FeatureDeserializer::DESCRIPTION; - constexpr StringLiteral FeatureDeserializer::DEPENDENCIES; + static Json::ArrayDeserializer array_instance; + }; + Json::ArrayDeserializer ArrayFeatureDeserializer::array_instance{ + "an array of feature objects"}; struct FeaturesFieldDeserializer : Json::IDeserializer>> { - virtual StringView type_name() const override { return "a features field"; } + virtual StringView type_name() const override { return "a set of features"; } virtual Span valid_fields() const override { return {}; } virtual Optional>> visit_array(Json::Reader& r, const Json::Array& arr) override { - return r.visit_value(arr, - Json::ArrayDeserializer{"an array of feature objects", - Json::AllowEmpty::Yes}); + return ArrayFeatureDeserializer::array_instance.visit_array(r, arr); } virtual Optional>> visit_object(Json::Reader& r, @@ -543,30 +561,31 @@ namespace vcpkg std::vector> res; std::vector extra_fields; - FeatureDeserializer deserializer; for (const auto& pr : obj) { if (!Json::IdentifierDeserializer::is_ident(pr.first)) { - extra_fields.push_back(pr.first.to_string()); + r.add_generic_error(type_name(), + "unexpected field '", + pr.first, + "': must be lowercase alphanumeric+hyphens and not reserved"); continue; } - deserializer.name.assign(pr.first.begin(), pr.first.end()); - auto field = r.visit_map_field(pr.first, pr.second, deserializer); - if (auto p = field.get()) + std::unique_ptr v; + r.visit_in_key(pr.second, pr.first, v, FeatureDeserializer::instance); + if (v) { - res.push_back(std::move(*p)); + v->name = pr.first.to_string(); + res.push_back(std::move(v)); } } - if (!extra_fields.empty()) - { - r.add_extra_fields_error(type_name(), std::move(extra_fields)); - } - return std::move(res); } + + static FeaturesFieldDeserializer instance; }; + FeaturesFieldDeserializer FeaturesFieldDeserializer::instance; static constexpr StringView EXPRESSION_WORDS[] = { "WITH", @@ -706,7 +725,10 @@ namespace vcpkg return sv.to_string(); } } + + static LicenseExpressionDeserializer instance; }; + LicenseExpressionDeserializer LicenseExpressionDeserializer::instance; struct ManifestDeserializer : Json::IDeserializer> { @@ -766,41 +788,41 @@ namespace vcpkg } } + static Json::StringDeserializer version_deserializer{"a version"}; + static Json::StringDeserializer url_deserializer{"a url"}; + constexpr static StringView type_name = "vcpkg.json"; - r.required_object_field(type_name, obj, NAME, spgh->name, Json::IdentifierDeserializer{}); - r.required_object_field(type_name, obj, VERSION, spgh->version, Json::StringDeserializer{"a version"}); - r.optional_object_field(obj, PORT_VERSION, spgh->port_version, Json::NaturalNumberDeserializer{}); - r.optional_object_field(obj, MAINTAINERS, spgh->maintainers, Json::ParagraphDeserializer{}); - r.optional_object_field(obj, DESCRIPTION, spgh->description, Json::ParagraphDeserializer{}); - r.optional_object_field(obj, HOMEPAGE, spgh->homepage, Json::StringDeserializer{"a url"}); - r.optional_object_field(obj, DOCUMENTATION, spgh->documentation, Json::StringDeserializer{"a url"}); - r.optional_object_field(obj, LICENSE, spgh->license, LicenseExpressionDeserializer{}); - r.optional_object_field( - obj, - DEPENDENCIES, - spgh->dependencies, - Json::ArrayDeserializer{"an array of dependencies", Json::AllowEmpty::Yes}); + r.required_object_field(type_name, obj, NAME, spgh->name, Json::IdentifierDeserializer::instance); + r.required_object_field(type_name, obj, VERSION, spgh->version, version_deserializer); + r.optional_object_field(obj, PORT_VERSION, spgh->port_version, Json::NaturalNumberDeserializer::instance); + r.optional_object_field(obj, MAINTAINERS, spgh->maintainers, Json::ParagraphDeserializer::instance); + r.optional_object_field(obj, DESCRIPTION, spgh->description, Json::ParagraphDeserializer::instance); + r.optional_object_field(obj, HOMEPAGE, spgh->homepage, url_deserializer); + r.optional_object_field(obj, DOCUMENTATION, spgh->documentation, url_deserializer); + r.optional_object_field(obj, LICENSE, spgh->license, LicenseExpressionDeserializer::instance); + r.optional_object_field(obj, DEPENDENCIES, spgh->dependencies, DependencyArrayDeserializer::instance); if (obj.contains(DEV_DEPENDENCIES)) { - System::print2(System::Color::error, "dev_dependencies are not yet supported"); + System::print2(System::Color::error, DEV_DEPENDENCIES, " are not yet supported"); Checks::exit_fail(VCPKG_LINE_INFO); } - r.optional_object_field(obj, SUPPORTS, spgh->supports_expression, PlatformExprDeserializer{}); + r.optional_object_field(obj, SUPPORTS, spgh->supports_expression, PlatformExprDeserializer::instance); - r.optional_object_field(obj, - DEFAULT_FEATURES, - spgh->default_features, - Json::ArrayDeserializer{"an array of identifiers", - Json::AllowEmpty::Yes}); + r.optional_object_field( + obj, DEFAULT_FEATURES, spgh->default_features, Json::IdentifierArrayDeserializer::instance); - r.optional_object_field(obj, FEATURES, control_file->feature_paragraphs, FeaturesFieldDeserializer{}); + r.optional_object_field( + obj, FEATURES, control_file->feature_paragraphs, FeaturesFieldDeserializer::instance); canonicalize(*control_file); return std::move(control_file); } + + static ManifestDeserializer instance; }; + ManifestDeserializer ManifestDeserializer::instance; constexpr StringLiteral ManifestDeserializer::NAME; constexpr StringLiteral ManifestDeserializer::VERSION; @@ -822,7 +844,7 @@ namespace vcpkg { Json::Reader reader; - auto res = reader.visit_value(manifest, ManifestDeserializer{}); + auto res = reader.visit(manifest, ManifestDeserializer::instance); if (!reader.errors().empty()) { diff --git a/src/vcpkg/vcpkgpaths.cpp b/src/vcpkg/vcpkgpaths.cpp index b76ed19d97eee4..cc51c6a2f6a248 100644 --- a/src/vcpkg/vcpkgpaths.cpp +++ b/src/vcpkg/vcpkgpaths.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -9,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -78,9 +80,9 @@ namespace vcpkg const fs::path& filepath) { Json::Reader reader; - auto deserializer = ConfigurationDeserializer(args); + ConfigurationDeserializer deserializer(args); - auto parsed_config_opt = reader.visit_value(obj, deserializer); + auto parsed_config_opt = reader.visit(obj, deserializer); if (!reader.errors().empty()) { System::print2(System::Color::error, "Errors occurred while parsing ", fs::u8string(filepath), "\n");