Merge pull request #256 into master
6a7087f1container: root: test: unit: common: PluggableSpace: add case for conversions (Aaron Fiore)ce12412dcontainer: root: common: PluggableSpace: add character checks/conversions (Aaron Fiore)736a71e9container: root: test: unit: common: PluggablePath: add case for invalid characters (Aaron Fiore)8bc6477ccontainer: root: common: PluggablePath: add checks for invalid characters (Aaron Fiore)d2ff942fcontainer: root: test: unit: common: PluggablePath: update/add case for invalid family (Aaron Fiore)b775992dcontainer: root: common: PluggablePath: expand parent path, refactor (Aaron Fiore)
This commit was merged in pull request #256.
This commit is contained in:
@@ -300,39 +300,56 @@ class PluggablePath
|
|||||||
//! \return NPI reference
|
//! \return NPI reference
|
||||||
auto& parse()
|
auto& parse()
|
||||||
{
|
{
|
||||||
// Parse out pseudo tag
|
// Invalid characters
|
||||||
const std::string type{m_pseudo.substr(0, m_pseudo.find('/'))};
|
const std::regex regex{"[a-zA-Z0-9/_\\-\\.]+"};
|
||||||
|
const std::string msg{"invalid characters in path"};
|
||||||
|
|
||||||
std::string pruned{m_pseudo};
|
if (!m_path.repo().empty())
|
||||||
pruned.erase(0, pruned.find('/') + 1);
|
throw_ex_if<type::RuntimeError>(
|
||||||
|
!std::regex_match(m_path.repo(), regex), msg);
|
||||||
|
|
||||||
|
if (!m_path.custom().empty())
|
||||||
|
throw_ex_if<type::RuntimeError>(
|
||||||
|
!std::regex_match(m_path.custom(), regex), msg);
|
||||||
|
|
||||||
|
throw_ex_if<type::RuntimeError>(!std::regex_match(m_pseudo, regex), msg);
|
||||||
|
|
||||||
|
// Parse out pseudo tag
|
||||||
|
auto pos = m_pseudo.find('/');
|
||||||
|
throw_ex_if<type::RuntimeError>(!pos, "no pseudo tag");
|
||||||
|
const std::string tag{m_pseudo.substr(0, pos)};
|
||||||
|
|
||||||
// Set family group
|
// Set family group
|
||||||
m_family = pruned;
|
m_family = m_pseudo;
|
||||||
|
m_family.erase(0, pos + 1);
|
||||||
// Set parent directory
|
throw_ex_if<type::RuntimeError>(m_family.empty(), "no family found");
|
||||||
m_parent = pruned.substr(0, pruned.find('/'));
|
|
||||||
|
|
||||||
// Set child (filename)
|
|
||||||
m_child = pruned.substr(pruned.find_last_of('/') + 1);
|
|
||||||
|
|
||||||
// Set absolute path
|
// Set absolute path
|
||||||
std::string absolute{pruned};
|
m_absolute = m_family;
|
||||||
if (type == "repo")
|
if (tag == "repo")
|
||||||
{
|
{
|
||||||
m_is_repo = true;
|
m_is_repo = true;
|
||||||
absolute.insert(0, m_path.repo());
|
m_absolute.insert(0, m_path.repo());
|
||||||
}
|
}
|
||||||
else if (type == "custom")
|
else if (tag == "custom")
|
||||||
{
|
{
|
||||||
m_is_custom = true;
|
m_is_custom = true;
|
||||||
absolute.insert(0, m_path.custom());
|
m_absolute.insert(0, m_path.custom());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw_ex<type::RuntimeError>(
|
throw_ex<type::RuntimeError>(
|
||||||
"must be of type 'repo/<relative>' or 'custom/<relative>'");
|
"must be of tag 'repo' or 'custom' | was given: '" + tag + "'");
|
||||||
}
|
}
|
||||||
m_absolute = std::move(absolute);
|
|
||||||
|
// Set parent path (director(y|ies))
|
||||||
|
pos = m_family.find_last_of('/');
|
||||||
|
m_parent = m_family.substr(0, pos);
|
||||||
|
|
||||||
|
// Set child (filename)
|
||||||
|
m_child = m_family.substr(pos + 1);
|
||||||
|
throw_ex_if<type::RuntimeError>(
|
||||||
|
m_child.empty() || m_child == m_parent, "child not found");
|
||||||
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
@@ -365,22 +382,21 @@ class PluggablePath
|
|||||||
const auto& operator()() const { return m_path; }
|
const auto& operator()() const { return m_path; }
|
||||||
|
|
||||||
public:
|
public:
|
||||||
//! \return The pluggable's pseudo-path
|
//! \return The pluggable's complete pseudo-path
|
||||||
const std::string& pseudo() const { return m_pseudo; }
|
const std::string& pseudo() const { return m_pseudo; }
|
||||||
|
|
||||||
//! \return The pluggable's operating system absolute path
|
//! \return The pluggable's operating system absolute path (with parsed pseudo-path)
|
||||||
const std::string& absolute() const { return m_absolute; }
|
const std::string& absolute() const { return m_absolute; }
|
||||||
|
|
||||||
// TODO(unassigned): relative() to current working dir
|
//! \return The pluggable's relative parent director(y|ies) derived from pseudo-path
|
||||||
|
//! \warning Trailing slash is removed
|
||||||
//! \return The pluggable's parent directory
|
|
||||||
//! \note This also represents the expected namespace used for pluggable auto-loading
|
//! \note This also represents the expected namespace used for pluggable auto-loading
|
||||||
const std::string& parent() const { return m_parent; }
|
const std::string& parent() const { return m_parent; }
|
||||||
|
|
||||||
//! \return The pluggable's child (filename)
|
//! \return The pluggable's child (filename)
|
||||||
const std::string& child() const { return m_child; }
|
const std::string& child() const { return m_child; }
|
||||||
|
|
||||||
//! \return The pluggable's group of parent member and child filename
|
//! \return The pluggable's group of parent and child members
|
||||||
const std::string& family() const { return m_family; }
|
const std::string& family() const { return m_family; }
|
||||||
|
|
||||||
//! \return true if pseudo-path describes a repository location
|
//! \return true if pseudo-path describes a repository location
|
||||||
@@ -392,7 +408,7 @@ class PluggablePath
|
|||||||
private:
|
private:
|
||||||
type::PluggablePath m_path;
|
type::PluggablePath m_path;
|
||||||
std::string m_pseudo, m_absolute;
|
std::string m_pseudo, m_absolute;
|
||||||
std::string m_parent, m_family, m_child;
|
std::string m_family, m_parent, m_child;
|
||||||
bool m_is_repo, m_is_custom;
|
bool m_is_repo, m_is_custom;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -405,8 +421,52 @@ class PluggablePath
|
|||||||
//! \since docker-finance 1.1.0
|
//! \since docker-finance 1.1.0
|
||||||
class PluggableSpace
|
class PluggableSpace
|
||||||
{
|
{
|
||||||
|
public:
|
||||||
|
//! \brief Parses (or re-parses) constructed types
|
||||||
|
//! \warning Only call this function after constructing if underlying type::PluggableSpace has been changed (post-construction)
|
||||||
|
//! \return NPI reference
|
||||||
|
auto& parse()
|
||||||
|
{
|
||||||
|
auto const parser = [](const std::string& space) -> std::string {
|
||||||
|
std::string parsed{space};
|
||||||
|
|
||||||
|
// NOTE: allowed to be empty (for now)
|
||||||
|
if (!parsed.empty())
|
||||||
|
{
|
||||||
|
throw_ex_if<type::RuntimeError>(
|
||||||
|
!std::regex_match(
|
||||||
|
parsed,
|
||||||
|
std::regex{
|
||||||
|
"[a-zA-Z0-9:/_\\-]+"} /* TODO(unassigned): refine */),
|
||||||
|
"invalid characters in namespace");
|
||||||
|
|
||||||
|
if (parsed.find('/'))
|
||||||
|
{
|
||||||
|
parsed = std::regex_replace(parsed, std::regex{"/"}, "::");
|
||||||
|
}
|
||||||
|
if (parsed.find('-'))
|
||||||
|
{
|
||||||
|
parsed = std::regex_replace(parsed, std::regex{"-"}, "_");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
};
|
||||||
|
|
||||||
|
m_space.outer(parser(m_space.outer()));
|
||||||
|
m_space.inner(parser(m_space.inner()));
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
explicit PluggableSpace(const type::PluggableSpace& space) : m_space(space) {}
|
// \note Since the current presumption is that a PluggableSpace is likely
|
||||||
|
// to be derived from an operating system path (via PluggablePath),
|
||||||
|
// there's leeway for path-to-namespace conversions to be done here.
|
||||||
|
explicit PluggableSpace(const type::PluggableSpace& space) : m_space(space)
|
||||||
|
{
|
||||||
|
parse();
|
||||||
|
}
|
||||||
~PluggableSpace() = default;
|
~PluggableSpace() = default;
|
||||||
|
|
||||||
PluggableSpace(const PluggableSpace&) = default;
|
PluggableSpace(const PluggableSpace&) = default;
|
||||||
|
|||||||
@@ -534,8 +534,8 @@ TEST_F(PluggablePath, Family_nested)
|
|||||||
Path repo("repo" + std::string{"/"} + nested),
|
Path repo("repo" + std::string{"/"} + nested),
|
||||||
custom("custom" + std::string{"/"} + nested);
|
custom("custom" + std::string{"/"} + nested);
|
||||||
|
|
||||||
ASSERT_EQ(repo.parent(), kParent);
|
ASSERT_EQ(repo.parent(), kParent + "/a_nested_dir");
|
||||||
ASSERT_EQ(custom.parent(), kParent);
|
ASSERT_EQ(custom.parent(), kParent + "/a_nested_dir");
|
||||||
|
|
||||||
ASSERT_EQ(repo.child(), kChild);
|
ASSERT_EQ(repo.child(), kChild);
|
||||||
ASSERT_EQ(custom.child(), kChild);
|
ASSERT_EQ(custom.child(), kChild);
|
||||||
@@ -544,6 +544,45 @@ TEST_F(PluggablePath, Family_nested)
|
|||||||
ASSERT_EQ(custom.family(), nested);
|
ASSERT_EQ(custom.family(), nested);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(PluggablePath, InvalidFamily)
|
||||||
|
{
|
||||||
|
ASSERT_THROW(Path path(""), common::type::RuntimeError);
|
||||||
|
ASSERT_THROW(Path path("nope"), common::type::RuntimeError);
|
||||||
|
|
||||||
|
ASSERT_THROW(Path path("repo/"), common::type::RuntimeError);
|
||||||
|
ASSERT_THROW(Path path("custom/"), common::type::RuntimeError);
|
||||||
|
|
||||||
|
ASSERT_THROW(Path path("repo/incomplete"), common::type::RuntimeError);
|
||||||
|
ASSERT_THROW(Path path("custom/incomplete"), common::type::RuntimeError);
|
||||||
|
|
||||||
|
ASSERT_THROW(Path path("repo/incomplete/"), common::type::RuntimeError);
|
||||||
|
ASSERT_THROW(Path path("custom/incomplete/"), common::type::RuntimeError);
|
||||||
|
|
||||||
|
ASSERT_THROW(
|
||||||
|
Path path("repo/incomplete/incomplete"), common::type::RuntimeError);
|
||||||
|
ASSERT_THROW(
|
||||||
|
Path path("custom/incomplete/incomplete"), common::type::RuntimeError);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(PluggablePath, InvalidCharacters)
|
||||||
|
{
|
||||||
|
ASSERT_THROW(Path path("repo/no spaces/a.file"), common::type::RuntimeError);
|
||||||
|
ASSERT_THROW(
|
||||||
|
Path path("custom/no spaces/a.file"), common::type::RuntimeError);
|
||||||
|
|
||||||
|
ASSERT_THROW(Path path("repo/'no_ticks'/a.file"), common::type::RuntimeError);
|
||||||
|
ASSERT_THROW(
|
||||||
|
Path path("custom/'no_ticks'/a.file"), common::type::RuntimeError);
|
||||||
|
|
||||||
|
ASSERT_THROW(Path path("repo/any/@a.file"), common::type::RuntimeError);
|
||||||
|
ASSERT_THROW(Path path("custom/any/@a.file"), common::type::RuntimeError);
|
||||||
|
|
||||||
|
ASSERT_THROW(
|
||||||
|
Path path("repo/bad\\ slash/a.file"), common::type::RuntimeError);
|
||||||
|
ASSERT_THROW(
|
||||||
|
Path path("custom/bad\\ slash/a.file"), common::type::RuntimeError);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(PluggablePath, Base)
|
TEST_F(PluggablePath, Base)
|
||||||
{
|
{
|
||||||
ASSERT_EQ(m_repo().repo(), kRepoBase);
|
ASSERT_EQ(m_repo().repo(), kRepoBase);
|
||||||
@@ -631,11 +670,10 @@ struct PluggableSpace : public ::testing::Test,
|
|||||||
|
|
||||||
struct Space : public ::dfi::common::PluggableSpace
|
struct Space : public ::dfi::common::PluggableSpace
|
||||||
{
|
{
|
||||||
using space = ::dfi::tests::unit::PluggableSpace;
|
|
||||||
explicit Space(const std::string_view outer, const std::string_view inner)
|
explicit Space(const std::string_view outer, const std::string_view inner)
|
||||||
: ::dfi::common::PluggableSpace(
|
: ::dfi::common::PluggableSpace(
|
||||||
::dfi::common::type::PluggableSpace{
|
::dfi::common::type::PluggableSpace{
|
||||||
{std::string{space::kOuter}, std::string{space::kInner}}})
|
{std::string{outer}, std::string{inner}}})
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -687,6 +725,29 @@ TEST_F(PluggableSpace, Mutators)
|
|||||||
ASSERT_EQ(m_space.inner(), kFour);
|
ASSERT_EQ(m_space.inner(), kFour);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(PluggableSpace, Conversions)
|
||||||
|
{
|
||||||
|
ASSERT_NO_THROW(Space space("", ""));
|
||||||
|
ASSERT_NO_THROW(Space space("hi/hey", "there/now"));
|
||||||
|
ASSERT_THROW(Space space("hi hey", "there now"), common::type::RuntimeError);
|
||||||
|
ASSERT_THROW(Space space("hi@hey", "there@now"), common::type::RuntimeError);
|
||||||
|
|
||||||
|
Space space("hi/hey", "there/now");
|
||||||
|
ASSERT_NO_THROW(space().outer("hi hey").inner("there now"));
|
||||||
|
ASSERT_THROW(space.parse(), common::type::RuntimeError);
|
||||||
|
ASSERT_NO_THROW(space().outer("hi@hey").inner("there@now"));
|
||||||
|
ASSERT_THROW(space.parse(), common::type::RuntimeError);
|
||||||
|
|
||||||
|
space = Space{"hi/hey", "there/now"};
|
||||||
|
ASSERT_EQ(space.outer(), "hi::hey");
|
||||||
|
ASSERT_EQ(space.inner(), "there::now");
|
||||||
|
|
||||||
|
ASSERT_NO_THROW(space().outer("hi-hey").inner("there-now"));
|
||||||
|
ASSERT_NO_THROW(space.parse());
|
||||||
|
ASSERT_EQ(space.outer(), "hi_hey");
|
||||||
|
ASSERT_EQ(space.inner(), "there_now");
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(PluggableSpace, Booleans)
|
TEST_F(PluggableSpace, Booleans)
|
||||||
{
|
{
|
||||||
ASSERT_EQ(m_space.has_outer(), true);
|
ASSERT_EQ(m_space.has_outer(), true);
|
||||||
|
|||||||
Reference in New Issue
Block a user