[vcpkg] Improve Json error messages (#12981)

* [vcpkg] Fix error reporting on json parse failure

* [vcpkg] Track manifest path for use in diagnostics

* [vcpkg] Use by-value for consumer API. Improve trailing comma diagnostic.

* [vcpkg] Track errors directly inside Json::Reader

* [vcpkg] Fixup use of .u8string()

Co-authored-by: Robert Schumacher <roschuma@microsoft.com>
This commit is contained in:
ras0219 2020-09-07 15:50:20 -07:00 committed by GitHub
parent 46a129decb
commit 0d0a84694c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 249 additions and 286 deletions

View File

@ -25,13 +25,13 @@ namespace vcpkg::Json
virtual Span<const StringView> valid_fields() const;
virtual Optional<Type> visit_null(Reader&, StringView);
virtual Optional<Type> visit_boolean(Reader&, StringView, bool);
virtual Optional<Type> visit_integer(Reader& r, StringView field_name, int64_t i);
virtual Optional<Type> visit_number(Reader&, StringView, double);
virtual Optional<Type> visit_string(Reader&, StringView, StringView);
virtual Optional<Type> visit_array(Reader&, StringView, const Array&);
virtual Optional<Type> visit_object(Reader&, StringView, const Object&);
virtual Optional<Type> visit_null(Reader&);
virtual Optional<Type> visit_boolean(Reader&, bool);
virtual Optional<Type> visit_integer(Reader& r, int64_t i);
virtual Optional<Type> visit_number(Reader&, double);
virtual Optional<Type> visit_string(Reader&, StringView);
virtual Optional<Type> visit_array(Reader&, const Array&);
virtual Optional<Type> visit_object(Reader&, const Object&);
protected:
IDeserializer() = default;

View File

@ -120,7 +120,7 @@ namespace vcpkg::Json
static Value boolean(bool) noexcept;
static Value integer(int64_t i) noexcept;
static Value number(double d) noexcept;
static Value string(StringView) noexcept;
static Value string(std::string s) noexcept;
static Value array(Array&&) noexcept;
static Value array(const Array&) noexcept;
static Value object(Object&&) noexcept;
@ -297,113 +297,94 @@ namespace vcpkg::Json
}
template<class Type>
Optional<Type> IDeserializer<Type>::visit_null(Reader&, StringView)
Optional<Type> IDeserializer<Type>::visit_null(Reader&)
{
return nullopt;
}
template<class Type>
Optional<Type> IDeserializer<Type>::visit_boolean(Reader&, StringView, bool)
Optional<Type> IDeserializer<Type>::visit_boolean(Reader&, bool)
{
return nullopt;
}
template<class Type>
Optional<Type> IDeserializer<Type>::visit_integer(Reader& r, StringView field_name, int64_t i)
Optional<Type> IDeserializer<Type>::visit_integer(Reader& r, int64_t i)
{
return this->visit_number(r, field_name, static_cast<double>(i));
return this->visit_number(r, static_cast<double>(i));
}
template<class Type>
Optional<Type> IDeserializer<Type>::visit_number(Reader&, StringView, double)
Optional<Type> IDeserializer<Type>::visit_number(Reader&, double)
{
return nullopt;
}
template<class Type>
Optional<Type> IDeserializer<Type>::visit_string(Reader&, StringView, StringView)
Optional<Type> IDeserializer<Type>::visit_string(Reader&, StringView)
{
return nullopt;
}
template<class Type>
Optional<Type> IDeserializer<Type>::visit_array(Reader&, StringView, const Array&)
Optional<Type> IDeserializer<Type>::visit_array(Reader&, const Array&)
{
return nullopt;
}
template<class Type>
Optional<Type> IDeserializer<Type>::visit_object(Reader&, StringView, const Object&)
Optional<Type> IDeserializer<Type>::visit_object(Reader&, const Object&)
{
return nullopt;
}
VCPKG_MSVC_WARNING(pop)
struct ReaderError
{
virtual void add_missing_field(std::string&& type, std::string&& key) = 0;
virtual void add_expected_type(std::string&& key, std::string&& expected_type) = 0;
virtual void add_extra_fields(std::string&& type, std::vector<std::string>&& fields) = 0;
virtual void add_mutually_exclusive_fields(std::string&& type, std::vector<std::string>&& fields) = 0;
virtual ~ReaderError() = default;
};
struct BasicReaderError : ReaderError
{
virtual void add_missing_field(std::string&& type, std::string&& key) override
{
missing_fields.emplace_back(std::move(type), std::move(key));
}
virtual void add_expected_type(std::string&& key, std::string&& expected_type) override
{
expected_types.emplace_back(std::move(key), std::move(expected_type));
}
virtual void add_extra_fields(std::string&& type, std::vector<std::string>&& fields) override
{
extra_fields.emplace_back(std::move(type), std::move(fields));
}
virtual void add_mutually_exclusive_fields(std::string&& type, std::vector<std::string>&& fields) override
{
mutually_exclusive_fields.emplace_back(std::move(type), std::move(fields));
}
bool has_error() const
{
return !missing_fields.empty() || !expected_types.empty() || !extra_fields.empty() ||
!mutually_exclusive_fields.empty();
}
std::vector<std::pair<std::string, std::string>> missing_fields;
std::vector<std::pair<std::string, std::string>> expected_types;
std::vector<std::pair<std::string, std::vector<std::string>>> extra_fields;
std::vector<std::pair<std::string, std::vector<std::string>>> mutually_exclusive_fields;
};
struct Reader
{
explicit Reader(ReaderError* err) : err(err) { }
const std::vector<std::string>& errors() const { return m_errors; }
std::vector<std::string>& errors() { return m_errors; }
ReaderError& error() const { return *err; }
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<std::string>&& fields)
{
for (auto&& field : fields)
m_errors.push_back(Strings::concat(path(), " (", type, "): ", "unexpected field '", field, '\''));
}
std::string path() const noexcept;
private:
ReaderError* err;
std::vector<std::string> m_errors;
struct Path
{
int64_t index = -1;
StringView field;
};
std::vector<Path> m_path;
template<class Type>
Optional<Type> internal_visit(const Value& value, StringView key, IDeserializer<Type>& visitor)
Optional<Type> internal_visit(const Value& value, IDeserializer<Type>& visitor)
{
switch (value.kind())
{
case ValueKind::Null: return visitor.visit_null(*this, key);
case ValueKind::Boolean: return visitor.visit_boolean(*this, key, value.boolean());
case ValueKind::Integer: return visitor.visit_integer(*this, key, value.integer());
case ValueKind::Number: return visitor.visit_number(*this, key, value.number());
case ValueKind::String: return visitor.visit_string(*this, key, value.string());
case ValueKind::Array: return visitor.visit_array(*this, key, value.array());
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, key, value.object());
return visitor.visit_object(*this, obj);
}
}
vcpkg::Checks::exit_fail(VCPKG_LINE_INFO);
vcpkg::Checks::unreachable(VCPKG_LINE_INFO);
}
// returns whether the field was found, not whether it was valid
@ -416,7 +397,8 @@ namespace vcpkg::Json
return false;
}
Optional<Type> opt = internal_visit(*value, key, visitor);
m_path.push_back({-1, key});
Optional<Type> opt = internal_visit(*value, visitor);
if (auto val = opt.get())
{
@ -424,149 +406,108 @@ namespace vcpkg::Json
}
else
{
err->add_expected_type(key.to_string(), visitor.type_name().to_string());
add_expected_type_error(visitor.type_name().to_string());
}
m_path.pop_back();
return true;
}
static std::vector<std::string> invalid_json_fields(const Json::Object& obj,
Span<const StringView> known_fields) noexcept
{
const auto field_is_unknown = [known_fields](StringView sv) {
// allow directives
if (sv.size() != 0 && *sv.begin() == '$')
{
return false;
}
return std::find(known_fields.begin(), known_fields.end(), sv) == known_fields.end();
};
std::vector<std::string> res;
for (const auto& kv : obj)
{
if (field_is_unknown(kv.first))
{
res.push_back(kv.first.to_string());
}
}
return res;
}
// 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<const StringView> valid_fields, StringView type_name)
{
if (valid_fields.size() == 0)
{
return;
}
auto extra_fields = invalid_json_fields(obj, valid_fields);
if (!extra_fields.empty())
{
error().add_extra_fields(type_name.to_string(), std::move(extra_fields));
}
}
void check_for_unexpected_fields(const Object& obj, Span<const StringView> valid_fields, StringView type_name);
public:
template<class Type>
template<class Type, class Deserializer>
void required_object_field(
StringView type, const Object& obj, StringView key, Type& place, IDeserializer<Type>& visitor)
StringView type, const Object& obj, StringView key, Type& place, Deserializer&& visitor)
{
if (!internal_field(obj, key, place, visitor))
{
err->add_missing_field(type.to_string(), key.to_string());
this->add_missing_field_error(type, key, visitor.type_name());
}
}
template<class Type>
void required_object_field(
StringView type, const Object& obj, StringView key, Type& place, IDeserializer<Type>&& visitor)
{
required_object_field(type, obj, key, place, visitor);
}
// returns whether key \in obj
template<class Type>
bool optional_object_field(const Object& obj, StringView key, Type& place, IDeserializer<Type>& visitor)
template<class Type, class Deserializer>
bool optional_object_field(const Object& obj, StringView key, Type& place, Deserializer&& visitor)
{
return internal_field(obj, key, place, visitor);
}
template<class Type>
bool optional_object_field(const Object& obj, StringView key, Type& place, IDeserializer<Type>&& visitor)
Optional<Type> visit_value(const Value& value, IDeserializer<Type>& visitor)
{
return optional_object_field(obj, key, place, visitor);
return internal_visit(value, visitor);
}
template<class Type>
Optional<Type> visit_value(const Value& value, IDeserializer<Type>&& visitor)
{
return visit_value(value, visitor);
}
template<class Type>
Optional<Type> visit_value(const Value& value, StringView key, IDeserializer<Type>& visitor)
Optional<Type> visit_value(const Array& value, IDeserializer<Type>& visitor)
{
return internal_visit(value, key, visitor);
return visitor.visit_array(*this, value);
}
template<class Type>
Optional<Type> visit_value(const Value& value, StringView key, IDeserializer<Type>&& visitor)
Optional<Type> visit_value(const Array& value, IDeserializer<Type>&& visitor)
{
return visit_value(value, key, visitor);
return visit_value(value, visitor);
}
template<class Type>
Optional<Type> visit_value(const Array& value, StringView key, IDeserializer<Type>& visitor)
{
return visitor.visit_array(*this, key, value);
}
template<class Type>
Optional<Type> visit_value(const Array& value, StringView key, IDeserializer<Type>&& visitor)
{
return visit_value(value, key, visitor);
}
template<class Type>
Optional<Type> visit_value(const Object& value, StringView key, IDeserializer<Type>& visitor)
Optional<Type> visit_value(const Object& value, IDeserializer<Type>& visitor)
{
check_for_unexpected_fields(value, visitor.valid_fields(), visitor.type_name());
return visitor.visit_object(*this, key, value);
return visitor.visit_object(*this, value);
}
template<class Type>
Optional<Type> visit_value(const Object& value, StringView key, IDeserializer<Type>&& visitor)
Optional<Type> visit_value(const Object& value, IDeserializer<Type>&& visitor)
{
return visit_value(value, key, visitor);
return visit_value(value, visitor);
}
template<class Type>
Optional<std::vector<Type>> array_elements(const Array& arr, StringView key, IDeserializer<Type>& visitor)
Optional<std::vector<Type>> array_elements(const Array& arr, IDeserializer<Type>& visitor)
{
std::vector<Type> result;
for (const auto& el : arr)
m_path.emplace_back();
for (size_t i = 0; i < arr.size(); ++i)
{
auto opt = internal_visit(el, key, visitor);
m_path.back().index = static_cast<int64_t>(i);
auto opt = internal_visit(arr[i], visitor);
if (auto p = opt.get())
{
result.push_back(std::move(*p));
}
else
{
return nullopt;
this->add_expected_type_error(visitor.type_name());
for (++i; i < arr.size(); ++i)
{
m_path.back().index = static_cast<int64_t>(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<class Type>
Optional<std::vector<Type>> array_elements(const Array& arr, StringView key, IDeserializer<Type>&& visitor)
Optional<std::vector<Type>> array_elements(const Array& arr, IDeserializer<Type>&& visitor)
{
return array_elements(arr, key, visitor);
return array_elements(arr, visitor);
}
};
struct StringDeserializer final : IDeserializer<std::string>
{
virtual StringView type_name() const override { return type_name_; }
virtual Optional<std::string> visit_string(Reader&, StringView, StringView sv) override
{
return sv.to_string();
}
virtual Optional<std::string> visit_string(Reader&, StringView sv) override { return sv.to_string(); }
explicit StringDeserializer(StringView type_name_) : type_name_(type_name_) { }
@ -577,14 +518,14 @@ namespace vcpkg::Json
struct PathDeserializer final : IDeserializer<fs::path>
{
virtual StringView type_name() const override { return "a path"; }
virtual Optional<fs::path> visit_string(Reader&, StringView, StringView sv) override { return fs::u8path(sv); }
virtual Optional<fs::path> visit_string(Reader&, StringView sv) override { return fs::u8path(sv); }
};
struct NaturalNumberDeserializer final : IDeserializer<int>
{
virtual StringView type_name() const override { return "a natural number"; }
virtual Optional<int> visit_integer(Reader&, StringView, int64_t value) override
virtual Optional<int> visit_integer(Reader&, int64_t value) override
{
if (value > std::numeric_limits<int>::max() || value < 0)
{
@ -598,7 +539,7 @@ namespace vcpkg::Json
{
virtual StringView type_name() const override { return "a boolean"; }
virtual Optional<bool> visit_boolean(Reader&, StringView, bool b) override { return b; }
virtual Optional<bool> visit_boolean(Reader&, bool b) override { return b; }
};
enum class AllowEmpty : bool
@ -619,13 +560,13 @@ namespace vcpkg::Json
{
}
virtual Optional<type> visit_array(Reader& r, StringView key, const Array& arr) override
virtual Optional<type> visit_array(Reader& r, const Array& arr) override
{
if (allow_empty_ == AllowEmpty::No && arr.size() == 0)
{
return nullopt;
}
return r.array_elements(arr, key, underlying_visitor_);
return r.array_elements(arr, underlying_visitor_);
}
private:
@ -638,16 +579,16 @@ namespace vcpkg::Json
{
virtual StringView type_name() const override { return "a string or array of strings"; }
virtual Optional<std::vector<std::string>> visit_string(Reader&, StringView, StringView sv) override
virtual Optional<std::vector<std::string>> visit_string(Reader&, StringView sv) override
{
std::vector<std::string> out;
out.push_back(sv.to_string());
return out;
}
virtual Optional<std::vector<std::string>> visit_array(Reader& r, StringView key, const Array& arr) override
virtual Optional<std::vector<std::string>> visit_array(Reader& r, const Array& arr) override
{
return r.array_elements(arr, key, StringDeserializer{"a string"});
return r.array_elements(arr, StringDeserializer{"a string"});
}
};
@ -658,7 +599,7 @@ namespace vcpkg::Json
// [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<std::string> visit_string(Json::Reader&, StringView, StringView sv) override
virtual Optional<std::string> visit_string(Json::Reader&, StringView sv) override
{
if (is_ident(sv))
{
@ -693,7 +634,7 @@ namespace vcpkg::Json
return true;
}
virtual Optional<std::string> visit_string(Json::Reader&, StringView, StringView sv) override
virtual Optional<std::string> visit_string(Json::Reader&, StringView sv) override
{
if (!is_package_name(sv))
{

View File

@ -29,7 +29,7 @@ namespace vcpkg
return t;
}
virtual Optional<Configuration> visit_object(Json::Reader& r, StringView, const Json::Object& obj) override;
virtual Optional<Configuration> visit_object(Json::Reader& r, const Json::Object& obj) override;
ConfigurationDeserializer(const VcpkgCmdArguments& args);

View File

@ -20,11 +20,13 @@ namespace vcpkg::Parse
std::map<std::string, std::vector<std::string>> extra_fields;
std::map<std::string, std::string> expected_types;
std::map<std::string, std::vector<std::string>> mutually_exclusive_fields;
std::vector<std::string> other_errors;
std::string error;
bool has_error() const
{
return !missing_fields.empty() || !extra_fields.empty() || !expected_types.empty() || !error.empty();
return !missing_fields.empty() || !extra_fields.empty() || !expected_types.empty() ||
!other_errors.empty() || !error.empty();
}
};

View File

@ -50,10 +50,8 @@ namespace vcpkg
virtual StringView type_name() const override;
virtual Span<const StringView> valid_fields() const override;
virtual Optional<std::unique_ptr<RegistryImpl>> visit_null(Json::Reader&, StringView) override;
virtual Optional<std::unique_ptr<RegistryImpl>> visit_object(Json::Reader&,
StringView,
const Json::Object&) override;
virtual Optional<std::unique_ptr<RegistryImpl>> visit_null(Json::Reader&) override;
virtual Optional<std::unique_ptr<RegistryImpl>> visit_object(Json::Reader&, const Json::Object&) override;
};
struct RegistryDeserializer final : Json::IDeserializer<Registry>
@ -63,7 +61,7 @@ namespace vcpkg
virtual StringView type_name() const override;
virtual Span<const StringView> valid_fields() const override;
virtual Optional<Registry> visit_object(Json::Reader&, StringView, const Json::Object&) override;
virtual Optional<Registry> visit_object(Json::Reader&, const Json::Object&) override;
};
// this type implements the registry fall back logic from the registries RFC:

View File

@ -108,7 +108,7 @@ namespace vcpkg
const std::string& get_tool_version(const std::string& tool) const;
Optional<const Json::Object&> get_manifest() const;
Optional<const Json::JsonStyle&> get_manifest_style() const;
Optional<const fs::path&> get_manifest_path() const;
const Configuration& get_configuration() const;
/// <summary>Retrieve a toolset matching a VS version</summary>

View File

@ -28,8 +28,8 @@ static std::string mystringify(const Value& val) { return Json::stringify(val, J
TEST_CASE ("JSON stringify weird strings", "[json]")
{
vcpkg::StringView str = U8_STR("😀 😁 😂 🤣 😃 😄 😅 😆 😉");
REQUIRE(mystringify(Value::string(str)) == ('"' + str.to_string() + "\"\n"));
std::string str = U8_STR("😀 😁 😂 🤣 😃 😄 😅 😆 😉");
REQUIRE(mystringify(Value::string(str)) == ('"' + str + "\"\n"));
REQUIRE(mystringify(Value::string("\xED\xA0\x80")) == "\"\\ud800\"\n"); // unpaired surrogate
}
@ -228,3 +228,14 @@ TEST_CASE ("JSON parse full file", "[json]")
}
REQUIRE(res);
}
TEST_CASE ("JSON track newlines", "[json]")
{
auto res = Json::parse("{\n,", fs::u8path("filename"));
REQUIRE(!res);
REQUIRE(res.error()->format() ==
R"(Error: filename:2:1: Unexpected character; expected property name
on expression: ,
^
)");
}

View File

@ -253,15 +253,15 @@ namespace vcpkg::Json
val.underlying_ = std::make_unique<ValueImpl>(ValueKindConstant<VK::Number>(), d);
return val;
}
Value Value::string(StringView sv) noexcept
Value Value::string(std::string s) noexcept
{
if (!Unicode::utf8_is_valid_string(sv.begin(), sv.end()))
if (!Unicode::utf8_is_valid_string(s.data(), s.data() + s.size()))
{
Debug::print("Invalid string: ", sv, '\n');
vcpkg::Checks::exit_with_message(VCPKG_LINE_INFO, "Invalid utf8 passed to Value::string(StringView)");
Debug::print("Invalid string: ", s, '\n');
vcpkg::Checks::exit_with_message(VCPKG_LINE_INFO, "Invalid utf8 passed to Value::string(std::string)");
}
Value val;
val.underlying_ = std::make_unique<ValueImpl>(ValueKindConstant<VK::String>(), sv.to_string());
val.underlying_ = std::make_unique<ValueImpl>(ValueKindConstant<VK::String>(), std::move(s));
return val;
}
Value Value::array(Array&& arr) noexcept
@ -489,10 +489,6 @@ namespace vcpkg::Json
{
return code_point == '-' || is_digit(code_point);
}
static bool is_keyword_start(char32_t code_point) noexcept
{
return code_point == 'f' || code_point == 'n' || code_point == 't';
}
static unsigned char from_hex_digit(char32_t code_point) noexcept
{
@ -807,6 +803,7 @@ namespace vcpkg::Json
}
else if (current == ',')
{
auto comma_loc = cur_loc();
next();
skip_whitespace();
current = cur();
@ -817,7 +814,7 @@ namespace vcpkg::Json
}
if (current == ']')
{
add_error("Trailing comma in array");
add_error("Trailing comma in array", comma_loc);
return Value::array(std::move(arr));
}
}
@ -903,6 +900,7 @@ namespace vcpkg::Json
}
else if (current == ',')
{
auto comma_loc = cur_loc();
next();
skip_whitespace();
current = cur();
@ -913,7 +911,7 @@ namespace vcpkg::Json
}
else if (current == '}')
{
add_error("Trailing comma in an object");
add_error("Trailing comma in an object", comma_loc);
return Value();
}
}
@ -1260,4 +1258,57 @@ namespace vcpkg::Json
}
// } auto stringify()
static std::vector<std::string> invalid_json_fields(const Json::Object& obj,
Span<const StringView> known_fields) noexcept
{
const auto field_is_unknown = [known_fields](StringView sv) {
// allow directives
if (sv.size() != 0 && *sv.begin() == '$')
{
return false;
}
return std::find(known_fields.begin(), known_fields.end(), sv) == known_fields.end();
};
std::vector<std::string> res;
for (const auto& kv : obj)
{
if (field_is_unknown(kv.first))
{
res.push_back(kv.first.to_string());
}
}
return res;
}
void Reader::check_for_unexpected_fields(const Object& obj,
Span<const StringView> valid_fields,
StringView type_name)
{
if (valid_fields.size() == 0)
{
return;
}
auto extra_fields = invalid_json_fields(obj, valid_fields);
if (!extra_fields.empty())
{
add_extra_fields_error(type_name.to_string(), std::move(extra_fields));
}
}
std::string Reader::path() const noexcept
{
std::string p("$");
for (auto&& s : m_path)
{
if (s.index < 0)
Strings::append(p, '.', s.field);
else
Strings::append(p, '[', s.index, ']');
}
return p;
}
}

View File

@ -66,10 +66,15 @@ namespace vcpkg::Parse
{
return Unicode::end_of_file;
}
auto ch = *m_it;
// See https://www.gnu.org/prep/standards/standards.html#Errors
advance_rowcol(*m_it, m_row, m_column);
advance_rowcol(ch, m_row, m_column);
++m_it;
if (ch == '\n')
{
m_start_of_line = m_it;
}
if (m_it != m_it.end() && Unicode::utf16_is_surrogate_code_point(*m_it))
{
m_it = m_it.end();

View File

@ -5,9 +5,7 @@
namespace vcpkg
{
Optional<Configuration> ConfigurationDeserializer::visit_object(Json::Reader& r,
StringView,
const Json::Object& obj)
Optional<Configuration> ConfigurationDeserializer::visit_object(Json::Reader& r, const Json::Object& obj)
{
RegistrySet registries;

View File

@ -788,13 +788,15 @@ namespace vcpkg::Install
{
pkgsconfig = fs::u8path(it_pkgsconfig->second);
}
auto maybe_manifest_scf = SourceControlFile::parse_manifest_file("manifest", *manifest);
auto manifest_path = paths.get_manifest_path().value_or_exit(VCPKG_LINE_INFO);
auto maybe_manifest_scf = SourceControlFile::parse_manifest_file(manifest_path, *manifest);
if (!maybe_manifest_scf)
{
print_error_message(maybe_manifest_scf.error());
Checks::exit_with_message(
VCPKG_LINE_INFO, "Failed to parse manifest %s/vcpkg.json.", fs::u8string(paths.manifest_root_dir));
System::print2(
"See https://github.com/Microsoft/vcpkg/tree/master/docs/specifications/manifests.md for "
"more information.\n");
Checks::exit_fail(VCPKG_LINE_INFO);
}
auto& manifest_scf = *maybe_manifest_scf.value_or_exit(VCPKG_LINE_INFO);

View File

@ -45,13 +45,9 @@ namespace vcpkg
return t;
}
Optional<std::unique_ptr<RegistryImpl>> RegistryImplDeserializer::visit_null(Json::Reader&, StringView)
{
return nullptr;
}
Optional<std::unique_ptr<RegistryImpl>> RegistryImplDeserializer::visit_null(Json::Reader&) { return nullptr; }
Optional<std::unique_ptr<RegistryImpl>> RegistryImplDeserializer::visit_object(Json::Reader& r,
StringView,
const Json::Object& obj)
{
std::string kind;
@ -62,14 +58,14 @@ namespace vcpkg
{
if (obj.contains(PATH))
{
r.error().add_extra_fields(type_name().to_string(), {PATH});
r.add_extra_fields_error("a builtin registry", {PATH});
}
return static_cast<std::unique_ptr<RegistryImpl>>(std::make_unique<BuiltinRegistry>());
}
else if (kind == KIND_DIRECTORY)
{
fs::path path;
r.required_object_field(type_name(), obj, PATH, path, Json::PathDeserializer{});
r.required_object_field("a directory registry", obj, PATH, path, Json::PathDeserializer{});
return static_cast<std::unique_ptr<RegistryImpl>>(std::make_unique<DirectoryRegistry>(std::move(path)));
}
@ -93,9 +89,9 @@ namespace vcpkg
return t;
}
Optional<Registry> RegistryDeserializer::visit_object(Json::Reader& r, StringView key, const Json::Object& obj)
Optional<Registry> RegistryDeserializer::visit_object(Json::Reader& r, const Json::Object& obj)
{
auto impl = RegistryImplDeserializer{}.visit_object(r, key, obj);
auto impl = RegistryImplDeserializer{}.visit_object(r, obj);
if (!impl.has_value())
{

View File

@ -361,7 +361,7 @@ namespace vcpkg
{
virtual StringView type_name() const override { return "a platform expression"; }
virtual Optional<PlatformExpression::Expr> visit_string(Json::Reader&, StringView, StringView sv) override
virtual Optional<PlatformExpression::Expr> visit_string(Json::Reader&, StringView sv) override
{
auto opt =
PlatformExpression::parse_platform_expression(sv, PlatformExpression::MultipleBinaryOperators::Deny);
@ -398,7 +398,7 @@ namespace vcpkg
return t;
}
virtual Optional<Dependency> visit_string(Json::Reader&, StringView, StringView sv) override
virtual Optional<Dependency> visit_string(Json::Reader&, StringView sv) override
{
if (!Json::PackageNameDeserializer::is_package_name(sv))
{
@ -410,7 +410,7 @@ namespace vcpkg
return dep;
}
virtual Optional<Dependency> visit_object(Json::Reader& r, StringView, const Json::Object& obj) override
virtual Optional<Dependency> visit_object(Json::Reader& r, const Json::Object& obj) override
{
Dependency dep;
@ -462,7 +462,6 @@ namespace vcpkg
}
virtual Optional<std::unique_ptr<FeatureParagraph>> visit_object(Json::Reader& r,
StringView,
const Json::Object& obj) override
{
auto feature = std::make_unique<FeatureParagraph>();
@ -516,7 +515,7 @@ namespace vcpkg
#include "spdx-licenses.inc"
;
virtual Optional<std::string> visit_string(Json::Reader&, StringView, StringView sv) override
virtual Optional<std::string> visit_string(Json::Reader&, StringView sv) override
{
Mode mode = Mode::ExpectExpression;
size_t open_parens = 0;
@ -677,7 +676,6 @@ namespace vcpkg
}
virtual Optional<std::unique_ptr<SourceControlFile>> visit_object(Json::Reader& r,
StringView,
const Json::Object& obj) override
{
auto control_file = std::make_unique<SourceControlFile>();
@ -752,44 +750,16 @@ namespace vcpkg
Parse::ParseExpected<SourceControlFile> SourceControlFile::parse_manifest_file(const fs::path& path_to_manifest,
const Json::Object& manifest)
{
struct JsonErr final : Json::ReaderError
Json::Reader reader;
auto res = reader.visit_value(manifest, ManifestDeserializer{});
if (!reader.errors().empty())
{
ParseControlErrorInfo pcei;
void add_missing_field(std::string&& type, std::string&& key) override
{
pcei.missing_fields[std::move(type)].push_back(std::move(key));
}
void add_expected_type(std::string&& key, std::string&& expected_type) override
{
pcei.expected_types.emplace(std::move(key), std::move(expected_type));
}
void add_extra_fields(std::string&& type, std::vector<std::string>&& fields) override
{
if (!fields.empty())
{
auto& fields_for_type = pcei.extra_fields[std::move(type)];
fields_for_type.insert(fields_for_type.end(), fields.begin(), fields.end());
}
}
void add_mutually_exclusive_fields(std::string&& type, std::vector<std::string>&& fields) override
{
if (!fields.empty())
{
auto& fields_for_type = pcei.mutually_exclusive_fields[std::move(type)];
fields_for_type.insert(fields_for_type.end(), fields.begin(), fields.end());
}
}
} err = {};
auto visit = Json::Reader{&err};
err.pcei.name = fs::u8string(path_to_manifest);
auto res = visit.visit_value(manifest, "$", ManifestDeserializer{});
if (err.pcei.has_error())
{
return std::make_unique<ParseControlErrorInfo>(std::move(err.pcei));
auto err = std::make_unique<ParseControlErrorInfo>();
err->name = fs::u8string(path_to_manifest);
err->other_errors = std::move(reader.errors());
return std::move(err);
}
else if (auto p = res.get())
{
@ -813,6 +783,13 @@ namespace vcpkg
System::print2(
System::Color::error, "Error: while loading ", error_info->name, ":\n", error_info->error, '\n');
}
if (!error_info->other_errors.empty())
{
System::print2(System::Color::error, "Errors occurred while parsing ", error_info->name, "\n");
for (auto&& msg : error_info->other_errors)
System::print2(" ", msg, '\n');
}
}
bool have_remaining_fields = false;
@ -839,9 +816,6 @@ namespace vcpkg
System::print2("This is the list of valid fields for CONTROL files (case-sensitive): \n\n ",
Strings::join("\n ", get_list_of_valid_fields()),
"\n\n");
System::print2("And this is the list of valid fields for manifest files: \n\n ",
Strings::join("\n ", ManifestDeserializer{}.valid_fields()),
"\n\n");
#if defined(_WIN32)
auto bootstrap = ".\\bootstrap-vcpkg.bat";
#else

View File

@ -84,50 +84,22 @@ namespace
namespace vcpkg
{
static Configuration deserialize_configuration(const Json::Object& obj, const VcpkgCmdArguments& args)
static Configuration deserialize_configuration(const Json::Object& obj,
const VcpkgCmdArguments& args,
const fs::path& filepath)
{
Json::BasicReaderError err;
Json::Reader reader{&err};
Json::Reader reader;
auto deserializer = ConfigurationDeserializer(args);
auto parsed_config_opt = reader.visit_value(obj, "$", deserializer);
if (err.has_error())
auto parsed_config_opt = reader.visit_value(obj, deserializer);
if (!reader.errors().empty())
{
if (!err.missing_fields.empty())
{
System::print2(System::Color::error, "Error: missing fields in configuration:\n");
for (const auto& missing : err.missing_fields)
{
System::printf(
System::Color::error, " %s was expected to have: %s\n", missing.first, missing.second);
}
}
if (!err.expected_types.empty())
{
System::print2(System::Color::error, "Error: Invalid types in configuration:\n");
for (const auto& expected : err.expected_types)
{
System::printf(
System::Color::error, " %s was expected to be %s\n", expected.first, expected.second);
}
}
if (!err.extra_fields.empty())
{
System::print2(System::Color::error, "Error: Invalid fields in configuration:\n");
for (const auto& extra : err.extra_fields)
{
System::printf(System::Color::error,
" %s had invalid fields: %s\n",
extra.first,
Strings::join(", ", extra.second));
}
}
if (!err.mutually_exclusive_fields.empty())
{
// this should never happen
Checks::unreachable(VCPKG_LINE_INFO);
}
System::print2(System::Color::error, "Errors occurred while parsing ", fs::u8string(filepath), "\n");
for (auto&& msg : reader.errors())
System::print2(" ", msg, '\n');
System::print2("See https://github.com/Microsoft/vcpkg/tree/master/docs/specifications/registries.md for "
"more information.\n");
Checks::exit_fail(VCPKG_LINE_INFO);
}
@ -157,7 +129,7 @@ namespace vcpkg
if (!manifest_opt.has_value())
{
Checks::exit_with_message(VCPKG_LINE_INFO,
"Failed to parse manifest at %s: %s",
"Failed to parse manifest at %s:\n%s",
fs::u8string(manifest_path),
manifest_opt.error()->format());
}
@ -216,7 +188,7 @@ namespace vcpkg
}
auto config_obj = std::move(parsed_config.first.object());
return {std::move(config_dir), deserialize_configuration(config_obj, args)};
return {std::move(config_dir), deserialize_configuration(config_obj, args, path_to_config)};
}
namespace details
@ -244,6 +216,7 @@ namespace vcpkg
fs::SystemHandle file_lock_handle;
Optional<std::pair<Json::Object, Json::JsonStyle>> m_manifest_doc;
fs::path m_manifest_path;
Configuration m_config;
};
}
@ -305,6 +278,7 @@ namespace vcpkg
}
m_pimpl->m_manifest_doc = load_manifest(filesystem, manifest_root_dir);
m_pimpl->m_manifest_path = manifest_root_dir / fs::u8path("vcpkg.json");
}
else
{
@ -495,6 +469,17 @@ If you wish to silence this error and use classic mode, you can:
return nullopt;
}
}
Optional<const fs::path&> VcpkgPaths::get_manifest_path() const
{
if (m_pimpl->m_manifest_doc)
{
return m_pimpl->m_manifest_path;
}
else
{
return nullopt;
}
}
const Configuration& VcpkgPaths::get_configuration() const { return m_pimpl->m_config; }