mirror of
https://github.com/mhx/dwarfs.git
synced 2025-08-03 17:56:12 -04:00
chore(metadata-requirements): support folly::dynamic requirements
This commit is contained in:
parent
a2e44d13d5
commit
c005b841c0
@ -619,6 +619,11 @@ if(WITH_TESTS)
|
||||
target_link_libraries(block_merger_test gtest gtest_main)
|
||||
list(APPEND TEST_TARGETS block_merger_test)
|
||||
|
||||
add_executable(dwarfs_metadata_requirements_test test/metadata_requirements_test.cpp)
|
||||
target_link_libraries(dwarfs_metadata_requirements_test test_helpers
|
||||
gtest gtest_main gmock_main)
|
||||
list(APPEND TEST_TARGETS dwarfs_metadata_requirements_test)
|
||||
|
||||
add_executable(dwarfs_pcm_sample_transformer_test test/pcm_sample_transformer_test.cpp)
|
||||
target_link_libraries(dwarfs_pcm_sample_transformer_test gtest gtest_main)
|
||||
list(APPEND TEST_TARGETS dwarfs_pcm_sample_transformer_test)
|
||||
|
@ -22,6 +22,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
@ -61,7 +62,8 @@ bool parse_metadata_requirements_set(T& container, folly::dynamic& req,
|
||||
|
||||
if (it->second[1].type() != folly::dynamic::ARRAY) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("non-array type argument for requirement '{}'", name));
|
||||
fmt::format("non-array type argument for requirement '{}', got '{}'",
|
||||
name, it->second[1].typeName()));
|
||||
}
|
||||
|
||||
for (auto v : it->second[1]) {
|
||||
@ -138,6 +140,21 @@ class checked_metadata_requirement_base : public metadata_requirement_base {
|
||||
virtual void check(Meta const& m) const = 0;
|
||||
};
|
||||
|
||||
class dynamic_metadata_requirement_base {
|
||||
public:
|
||||
virtual ~dynamic_metadata_requirement_base() = default;
|
||||
|
||||
dynamic_metadata_requirement_base(std::string const& name)
|
||||
: name_{name} {}
|
||||
|
||||
virtual void check(folly::dynamic const& m) const = 0;
|
||||
|
||||
std::string_view name() const { return name_; }
|
||||
|
||||
private:
|
||||
std::string const name_;
|
||||
};
|
||||
|
||||
template <typename Meta, typename T, typename U>
|
||||
class typed_metadata_requirement_base
|
||||
: public checked_metadata_requirement_base<Meta> {
|
||||
@ -188,8 +205,9 @@ class metadata_requirement_set
|
||||
protected:
|
||||
void check_value(T const& value) const override {
|
||||
if (set_ && set_->count(value) == 0) {
|
||||
throw std::range_error(fmt::format("{} '{}' does not meet requirements",
|
||||
this->name(), value));
|
||||
throw std::range_error(
|
||||
fmt::format("{} '{}' does not meet requirements [{}]", this->name(),
|
||||
value, fmt::join(*set_, ", ")));
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,4 +306,17 @@ class compression_metadata_requirements<void> {
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
class compression_metadata_requirements<folly::dynamic> {
|
||||
public:
|
||||
compression_metadata_requirements(std::string const& req);
|
||||
compression_metadata_requirements(folly::dynamic const& req);
|
||||
|
||||
void check(std::string const& meta) const;
|
||||
void check(folly::dynamic const& meta) const;
|
||||
|
||||
private:
|
||||
std::vector<std::unique_ptr<detail::dynamic_metadata_requirement_base>> req_;
|
||||
};
|
||||
|
||||
} // namespace dwarfs
|
||||
|
@ -21,16 +21,21 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <folly/json.h>
|
||||
|
||||
#include "dwarfs/compression_metadata_requirements.h"
|
||||
|
||||
namespace dwarfs::detail {
|
||||
namespace dwarfs {
|
||||
|
||||
namespace detail {
|
||||
|
||||
void check_dynamic_common(folly::dynamic const& dyn,
|
||||
std::string_view expected_type, size_t expected_size,
|
||||
std::string_view name) {
|
||||
if (dyn.type() != folly::dynamic::ARRAY) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("found non-array type for requirement '{}'", name));
|
||||
fmt::format("found non-array type for requirement '{}', got type '{}'",
|
||||
name, dyn.typeName()));
|
||||
}
|
||||
if (dyn.empty()) {
|
||||
throw std::runtime_error(
|
||||
@ -56,8 +61,181 @@ void check_unsupported_metadata_requirements(folly::dynamic& req) {
|
||||
}
|
||||
std::sort(keys.begin(), keys.end());
|
||||
throw std::runtime_error(fmt::format(
|
||||
"unsupported metadata requirements: {}", folly::join(", ", keys)));
|
||||
"unsupported metadata requirements: {}", fmt::join(keys, ", ")));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dwarfs::detail
|
||||
template <typename T>
|
||||
class dynamic_metadata_requirement_set
|
||||
: public dynamic_metadata_requirement_base {
|
||||
public:
|
||||
static_assert(std::is_same_v<T, std::string> || std::is_integral_v<T>);
|
||||
|
||||
dynamic_metadata_requirement_set(std::string const& name,
|
||||
folly::dynamic const& req)
|
||||
: dynamic_metadata_requirement_base{name} {
|
||||
auto tmp = req;
|
||||
if (!parse_metadata_requirements_set(set_, tmp, name,
|
||||
detail::value_parser<T>)) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("could not parse set requirement '{}'", name));
|
||||
}
|
||||
}
|
||||
|
||||
void check(folly::dynamic const& dyn) const override {
|
||||
if constexpr (std::is_same_v<T, std::string>) {
|
||||
if (!dyn.isString()) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("non-string type for requirement '{}', got type '{}'",
|
||||
name(), dyn.typeName()));
|
||||
}
|
||||
|
||||
if (set_.find(dyn.asString()) == set_.end()) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("{} '{}' does not meet requirements [{}]", name(),
|
||||
dyn.asString(), fmt::join(ordered_set(), ", ")));
|
||||
}
|
||||
} else {
|
||||
if (!dyn.isInt()) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("non-integral type for requirement '{}', got type '{}'",
|
||||
name(), dyn.typeName()));
|
||||
}
|
||||
|
||||
if (set_.find(dyn.asInt()) == set_.end()) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("{} '{}' does not meet requirements [{}]", name(),
|
||||
dyn.asInt(), fmt::join(ordered_set(), ", ")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<T> ordered_set() const {
|
||||
std::vector<T> result;
|
||||
result.reserve(set_.size());
|
||||
std::copy(set_.begin(), set_.end(), std::back_inserter(result));
|
||||
std::sort(result.begin(), result.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unordered_set<T> set_;
|
||||
};
|
||||
|
||||
class dynamic_metadata_requirement_range
|
||||
: public dynamic_metadata_requirement_base {
|
||||
public:
|
||||
dynamic_metadata_requirement_range(std::string const& name,
|
||||
folly::dynamic const& req)
|
||||
: dynamic_metadata_requirement_base{name} {
|
||||
auto tmp = req;
|
||||
if (!parse_metadata_requirements_range(min_, max_, tmp, name,
|
||||
detail::value_parser<int64_t>)) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("could not parse range requirement '{}'", name));
|
||||
}
|
||||
}
|
||||
|
||||
void check(folly::dynamic const& dyn) const override {
|
||||
if (!dyn.isInt()) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("non-integral type for requirement '{}', got type '{}'",
|
||||
name(), dyn.typeName()));
|
||||
}
|
||||
|
||||
auto v = dyn.asInt();
|
||||
|
||||
if (v < min_ || v > max_) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("{} '{}' does not meet requirements [{}, {}]", name(), v,
|
||||
min_, max_));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
int64_t min_, max_;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
compression_metadata_requirements<
|
||||
folly::dynamic>::compression_metadata_requirements(std::string const& req)
|
||||
: compression_metadata_requirements(folly::parseJson(req)) {}
|
||||
|
||||
compression_metadata_requirements<folly::dynamic>::
|
||||
compression_metadata_requirements(folly::dynamic const& req) {
|
||||
if (req.type() != folly::dynamic::OBJECT) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("metadata requirements must be an object, got type '{}'",
|
||||
req.typeName()));
|
||||
}
|
||||
|
||||
for (auto const& [k, v] : req.items()) {
|
||||
if (v.type() != folly::dynamic::ARRAY) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("requirement '{}' must be an array, got type '{}'",
|
||||
k.asString(), v.typeName()));
|
||||
}
|
||||
|
||||
if (v.size() < 2) {
|
||||
throw std::runtime_error(
|
||||
fmt::format("requirement '{}' must be an array of at least 2 "
|
||||
"elements, got only {}",
|
||||
k.asString(), v.size()));
|
||||
}
|
||||
|
||||
if (v[0].type() != folly::dynamic::STRING) {
|
||||
throw std::runtime_error(fmt::format(
|
||||
"type for requirement '{}' must be a string, got type '{}'",
|
||||
k.asString(), v[0].typeName()));
|
||||
}
|
||||
|
||||
if (v[0].asString() == "set") {
|
||||
if (v[1].type() != folly::dynamic::ARRAY) {
|
||||
throw std::runtime_error(fmt::format(
|
||||
"set for requirement '{}' must be an array, got type '{}'",
|
||||
k.asString(), v[1].typeName()));
|
||||
}
|
||||
if (v[1].empty()) {
|
||||
throw std::runtime_error(fmt::format(
|
||||
"set for requirement '{}' must not be empty", k.asString()));
|
||||
}
|
||||
if (v[1][0].isString()) {
|
||||
req_.emplace_back(
|
||||
std::make_unique<
|
||||
detail::dynamic_metadata_requirement_set<std::string>>(
|
||||
k.asString(), req));
|
||||
} else {
|
||||
req_.emplace_back(
|
||||
std::make_unique<detail::dynamic_metadata_requirement_set<int64_t>>(
|
||||
k.asString(), req));
|
||||
}
|
||||
} else if (v[0].asString() == "range") {
|
||||
req_.emplace_back(
|
||||
std::make_unique<detail::dynamic_metadata_requirement_range>(
|
||||
k.asString(), req));
|
||||
} else {
|
||||
throw std::runtime_error(
|
||||
fmt::format("unsupported requirement type '{}'", v[0].asString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void compression_metadata_requirements<folly::dynamic>::check(
|
||||
folly::dynamic const& dyn) const {
|
||||
for (auto const& r : req_) {
|
||||
if (auto it = dyn.find(r->name()); it != dyn.items().end()) {
|
||||
r->check(it->second);
|
||||
} else {
|
||||
throw std::runtime_error(
|
||||
fmt::format("missing requirement '{}'", r->name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void compression_metadata_requirements<folly::dynamic>::check(
|
||||
std::string const& metadata) const {
|
||||
check(folly::parseJson(metadata));
|
||||
}
|
||||
|
||||
} // namespace dwarfs
|
||||
|
93
test/metadata_requirements_test.cpp
Normal file
93
test/metadata_requirements_test.cpp
Normal file
@ -0,0 +1,93 @@
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/**
|
||||
* \author Marcus Holland-Moritz (github@mhxnet.de)
|
||||
* \copyright Copyright (c) Marcus Holland-Moritz
|
||||
*
|
||||
* This file is part of dwarfs.
|
||||
*
|
||||
* dwarfs is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* dwarfs is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with dwarfs. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "dwarfs/compression_metadata_requirements.h"
|
||||
|
||||
using namespace dwarfs;
|
||||
|
||||
TEST(metadata_requirements, dynamic_test) {
|
||||
std::string requirements = R"({
|
||||
"compression": ["set", ["lz4", "zstd"]],
|
||||
"block_size": ["range", 16, 1024]
|
||||
})";
|
||||
|
||||
std::unique_ptr<compression_metadata_requirements<folly::dynamic>> req;
|
||||
|
||||
ASSERT_NO_THROW(
|
||||
req = std::make_unique<compression_metadata_requirements<folly::dynamic>>(
|
||||
requirements));
|
||||
{
|
||||
std::string metadata = R"({
|
||||
"compression": "lz4",
|
||||
"block_size": 256
|
||||
})";
|
||||
|
||||
EXPECT_NO_THROW(req->check(metadata));
|
||||
}
|
||||
|
||||
{
|
||||
std::string metadata = R"({
|
||||
"compression": "lz4",
|
||||
"foo": "bar",
|
||||
"block_size": 256
|
||||
})";
|
||||
|
||||
EXPECT_NO_THROW(req->check(metadata));
|
||||
}
|
||||
|
||||
{
|
||||
std::string metadata = R"({
|
||||
"compression": "lzma",
|
||||
"block_size": 256
|
||||
})";
|
||||
|
||||
EXPECT_THAT(
|
||||
[&]() { req->check(metadata); },
|
||||
ThrowsMessage<std::runtime_error>(testing::HasSubstr(
|
||||
"compression 'lzma' does not meet requirements [lz4, zstd]")));
|
||||
}
|
||||
|
||||
{
|
||||
std::string metadata = R"({
|
||||
"block_size": 256
|
||||
})";
|
||||
|
||||
EXPECT_THAT([&]() { req->check(metadata); },
|
||||
ThrowsMessage<std::runtime_error>(
|
||||
testing::HasSubstr("missing requirement 'compression'")));
|
||||
}
|
||||
|
||||
{
|
||||
std::string metadata = R"({
|
||||
"compression": "zstd",
|
||||
"block_size": 8
|
||||
})";
|
||||
|
||||
EXPECT_THAT([&]() { req->check(metadata); },
|
||||
ThrowsMessage<std::runtime_error>(testing::HasSubstr(
|
||||
"block_size '8' does not meet requirements [16, 1024]")));
|
||||
}
|
||||
}
|
@ -85,7 +85,7 @@ TEST(pcmaudio_categorizer, requirements) {
|
||||
EXPECT_THAT(
|
||||
ent.output,
|
||||
MatchesRegex(
|
||||
R"(^\[WAV\] ".*": endianness 'little' does not meet requirements$)"));
|
||||
R"(^\[WAV\] ".*": endianness 'little' does not meet requirements \[\]$)"));
|
||||
|
||||
EXPECT_TRUE(frag.empty());
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user