Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restore support for model:// URIs #172

Merged
merged 8 commits into from
Feb 20, 2021
Merged

Restore support for model:// URIs #172

merged 8 commits into from
Feb 20, 2021

Conversation

chapulina
Copy link
Contributor

Summary

While working on gazebo-tooling/release-tools#361, I noticed that the changes in the URI introduced in ign-common4 were very disruptive, see gazebosim/gz-fuel-tools#163 (comment).

This is a proposal to reduce the impact of these changes and maintain support for the "URI" format that is widespread in the ecosystem (model:// / package://). Another goal of this PR is to make the changes more evident to downstream users through deprecation warnings.

Because there was no concept of "authority" up to ign-common3, it's safe to assume that users are currently embedding the authority into the path. This PR deprecates URI::Path() to hint to the user that something has changed and adds an alternative function, PathSegments, that does the same. When the user checks the migration guide, there are instructions to use Authority + PathSegments.

One thing I considered was maintaining the behaviour of Path as it was before, that is, it would set / get both authority and path segments. I could look into it if it sounds interesting.

Test it

Checklist

  • Signed all commits for DCO
  • Added tests
  • Added example world and/or tutorial
  • Updated documentation (as needed)
  • Updated migration guide (as needed)
  • codecheck passed (See contributing)
  • All tests passed (See test coverage)
  • While waiting for a review on your PR, please help review
    another open pull request
    to support the maintainers

Note to maintainers: Remember to use Squash-Merge

Signed-off-by: Louise Poubel <[email protected]>
@chapulina chapulina requested a review from mjcarroll as a code owner February 12, 2021 05:14
@chapulina chapulina requested a review from nkoenig February 12, 2021 05:14
@codecov
Copy link

codecov bot commented Feb 12, 2021

Codecov Report

Merging #172 (f805f5a) into main (2acfcf5) will increase coverage by 0.10%.
The diff coverage is 93.75%.

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #172      +/-   ##
==========================================
+ Coverage   76.30%   76.40%   +0.10%     
==========================================
  Files          71       71              
  Lines       10049    10080      +31     
==========================================
+ Hits         7668     7702      +34     
+ Misses       2381     2378       -3     
Impacted Files Coverage Δ
src/SystemPaths.cc 86.49% <87.50%> (-1.72%) ⬇️
src/URI.cc 97.39% <95.83%> (+1.67%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 2acfcf5...f805f5a. Read the comment docs.

public: URIPath &Path();
/// \deprecated Use `PathSegments()`, the path doesn't contain the
/// authority anymore.
public: URIPath IGN_DEPRECATED(4.0) &Path();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Path and PathSegments functions have the same behavior, so this deprecation is strange. An end user could read the deprecation warning as "Just switch to PathSegments(), and my code should work".

Remove PathSegments and change Path() to Path(bool _includeAuthority=true). The body of Path could output a warning message when _includeAuthority==true. Line 316 in src/SystemPaths.cc could use the value of _includeAuthority to know it should use _uri.Authority().Host() or not.

Another option would be to add functions to set/get a class variable called bool pathIncludeAuthority = true. The behavior is the same as above, but easier to track and document. We can then tick-tock the value of pathIncludeAuthority at some point.

Copy link
Contributor Author

@chapulina chapulina Feb 13, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started working on the 2nd option with the goal of making the whole class support "included authority mode":

Diff
diff --git a/include/ignition/common/SystemPaths.hh b/include/ignition/common/SystemPaths.hh
index f6c58a2..4899e29 100644
--- a/include/ignition/common/SystemPaths.hh
+++ b/include/ignition/common/SystemPaths.hh
@@ -75,8 +75,9 @@ namespace ignition
       public: std::string FindFileURI(const std::string &_uri) const;
 
       /// \brief Find a file or path using a URI.
-      /// If URI is not an absolute path, the URI's "Host / PathSegments"
-      /// will be matched against all added paths and environment variables.
+      /// If URI is not an absolute path, the URI's path will be matched against
+      /// all added paths and environment variables (including URI authority as
+      /// needed).
       /// \param[in] _uri the uniform resource identifier
       /// \return Returns full path name to file with platform-specific
       /// directory separators, or an empty string if URI couldn't be found.
diff --git a/include/ignition/common/URI.hh b/include/ignition/common/URI.hh
index c2aa1b9..ca44626 100644
--- a/include/ignition/common/URI.hh
+++ b/include/ignition/common/URI.hh
@@ -211,6 +211,16 @@ namespace ignition
       /// and a Windows drive specifier (e.g. 'C:') is pushed to it.
       public: void PushBack(const std::string &_part);
 
+      /// \brief Remove the part that's in the front of this path and return it.
+      /// \return Popped part.
+      /// Returns empty string if path doesn't have parts to be popped.
+      public: std::string PopFront();
+
+      /// \brief Remove the part that's in the back of this path and return it.
+      /// \return Popped part.
+      /// Returns empty string if path doesn't have parts to be popped.
+      public: std::string PopBack();
+
       /// \brief Compound assignment operator.
       /// \param[in] _part A new path to append.
       /// \return A new Path that consists of "this / _part"
@@ -393,7 +403,10 @@ namespace ignition
 
       /// \brief Construct a URI object from a string.
       /// \param[in] _str A string.
-      public: explicit URI(const std::string &_str);
+      /// \param[in] _pathIncludeAuthority True if the path should include the
+      /// authority.
+      public: explicit URI(const std::string &_str,
+          bool _pathIncludeAuthority = true);
 
       /// \brief Copy constructor
       /// \param[in] _uri Another URI.
@@ -420,9 +433,9 @@ namespace ignition
       /// \param[in] _scheme New scheme.
       public: void SetScheme(const std::string &_scheme);
 
-      /// \brief Get a mutable version of the URI's authority.
-      /// \return The authority
-      public: URIAuthority &Authority();
+      /// \brief Set the URI's authority.
+      /// \param[in] _authority The new authority.
+      public: void SetAuthority(const URIAuthority &_authority);
 
       /// \brief Get a const reference of the URI's authority.
       /// \return The authority
@@ -430,25 +443,27 @@ namespace ignition
 
       /// \brief Get a mutable version of the path component
       /// \return A reference to the path
-      /// \deprecated Use `PathSegments()`, the path doesn't contain the
-      /// authority anymore.
+      /// \deprecated Use `SetPath` to modify path. Using this method to modify
+      /// it may cause authority inconsistencies.
       public: URIPath IGN_DEPRECATED(4.0) &Path();
 
+      /// \brief Set the URI's path.
+      /// \param[in] _path The new path.
+      public: void SetPath(const URIPath &_path);
+
       /// \brief Get a const reference of the path component.
       /// \return A const reference of the path.
-      /// \deprecated Use `PathSegments()`, the path doesn't contain the
-      /// authority anymore.
-      public: const URIPath IGN_DEPRECATED(4.0) &Path() const;
+      public: const URIPath &Path() const;
 
-      /// \brief Get a mutable version of the path component.
-      /// The path doesn't contain the authority.
-      /// \return A reference to the path
-      public: URIPath &PathSegments();
+      /// \brief Set whether the path should include the authority.
+      /// Defaults to true, which is the legacy behaviour.
+      /// \param[in] _include True to include the authority in the path.
+      public: void SetPathIncludeAuthority(bool _include);
 
-      /// \brief Get a const reference of the path component.
-      /// The path doesn't contain the authority.
-      /// \return A const reference of the path.
-      public: const URIPath &PathSegments() const;
+      /// \brief Get whether the path includes the authority.
+      /// Defaults to true, which is the legacy behaviour.
+      /// \return True to include the authority in the path.
+      public: bool PathIncludeAuthority() const;
 
       /// \brief Get a mutable version of the query component
       /// \return A reference to the query
diff --git a/src/SystemPaths.cc b/src/SystemPaths.cc
index 5cfb099..df3762a 100644
--- a/src/SystemPaths.cc
+++ b/src/SystemPaths.cc
@@ -313,10 +313,23 @@ std::string SystemPaths::FindFileURI(const std::string &_uri) const
 std::string SystemPaths::FindFileURI(const ignition::common::URI &_uri) const
 {
   std::string prefix = _uri.Scheme();
-  std::string suffix = _uri.Authority().Host() + _uri.PathSegments().Str() +
-      _uri.Query().Str();
+  std::string suffix = _uri.Authority().Str().substr(2);
+  if (_uri.PathIncludeAuthority() && suffix.size() > 2u)
+  {
+    auto pathCopy = _uri.Path();
+    pathCopy.PopFront();
+    suffix += pathCopy.Str();
+  }
+  else
+  {
+    suffix += _uri.Path().Str();
+  }
+
+  suffix += _uri.Query().Str();
   std::string filename;
 
+ignerr << suffix << std::endl;
+
   if (prefix == "file")
   {
     // First try to find the file on the current system
@@ -421,6 +434,7 @@ std::string SystemPaths::FindFile(const std::string &_filename,
     for (const std::string &filePath : this->dataPtr->filePaths)
     {
       auto withSuffix = NormalizeDirectoryPath(filePath) + filename;
+ignerr << withSuffix << std::endl;
       if (exists(withSuffix))
       {
         path = ignition::common::copyFromUnixPath(withSuffix);
diff --git a/src/SystemPaths_TEST.cc b/src/SystemPaths_TEST.cc
index ce04b6f..dbc80a7 100644
--- a/src/SystemPaths_TEST.cc
+++ b/src/SystemPaths_TEST.cc
@@ -239,7 +239,7 @@ TEST_F(SystemPathsFixture, FindFileURI)
   auto robot2Cb = [dir2](const ignition::common::URI &_uri)
   {
     return _uri.Scheme() == "robot" ?
-           ignition::common::joinPaths(dir2, _uri.PathSegments().Str()) : "";
+           ignition::common::joinPaths(dir2, _uri.Path().Str()) : "";
   };
 
   EXPECT_EQ("", sp.FindFileURI("robot://test_f1"));
diff --git a/src/URI.cc b/src/URI.cc
index ca8eadf..6358e62 100644
--- a/src/URI.cc
+++ b/src/URI.cc
@@ -102,6 +102,9 @@ class ignition::common::URIPrivate
 
   /// \brief Fragment component.
   public: URIFragment fragment;
+
+  /// \brief Whether the path includes the authority
+  public: bool pathIncludeAuthority{true};
 };
 
 //////////////////////////////////////////////////
@@ -513,6 +516,28 @@ void URIPath::PushBack(const std::string &_part)
   this->dataPtr->path.push_back(part);
 }
 
+/////////////////////////////////////////////////
+std::string URIPath::PopFront()
+{
+  if (this->dataPtr->path.size() == 0)
+    return std::string();
+
+  auto result = this->dataPtr->path.front();
+  this->dataPtr->path.pop_front();
+  return result;
+}
+
+/////////////////////////////////////////////////
+std::string URIPath::PopBack()
+{
+  if (this->dataPtr->path.size() == 0)
+    return std::string();
+
+  auto result = this->dataPtr->path.back();
+  this->dataPtr->path.pop_back();
+  return result;
+}
+
 /////////////////////////////////////////////////
 const URIPath URIPath::operator/(const std::string &_part) const
 {
@@ -906,9 +931,10 @@ URI::URI()
 }
 
 /////////////////////////////////////////////////
-URI::URI(const std::string &_str)
+URI::URI(const std::string &_str, bool _pathIncludeAuthority)
   : URI()
 {
+  this->dataPtr->pathIncludeAuthority = _pathIncludeAuthority;
   this->Parse(_str);
 }
 
@@ -928,10 +954,22 @@ URI::~URI()
 std::string URI::Str() const
 {
   std::string result =
-    this->dataPtr->scheme.empty() ? "" : this->dataPtr->scheme + ":";
-  result += this->dataPtr->authority.Str() +
-            this->dataPtr->path.Str() +
-            this->dataPtr->query.Str() +
+    this->dataPtr->scheme.empty() ? "" : this->dataPtr->scheme + kSchemeDelim;
+  result += this->dataPtr->authority.Str();
+
+  if (this->dataPtr->pathIncludeAuthority &&
+      this->dataPtr->authority.Str().size() > 2u)
+  {
+    auto pathCopy = this->dataPtr->path;
+    pathCopy.PopFront();
+    result += pathCopy.Str();
+  }
+  else
+  {
+    result += this->dataPtr->path.Str();
+  }
+
+  result += this->dataPtr->query.Str() +
             this->dataPtr->fragment.Str();
   return result;
 }
@@ -949,9 +987,14 @@ void URI::SetScheme(const std::string &_scheme)
 }
 
 /////////////////////////////////////////////////
-URIAuthority &URI::Authority()
+void URI::SetAuthority(const URIAuthority &_authority)
 {
-  return this->dataPtr->authority;
+  this->dataPtr->authority = _authority;
+  if (this->dataPtr->pathIncludeAuthority)
+  {
+    this->dataPtr->path.PopFront();
+    this->dataPtr->path.PushFront(_authority.Str().substr(2));
+  }
 }
 
 /////////////////////////////////////////////////
@@ -966,6 +1009,17 @@ URIPath &URI::Path()
   return this->dataPtr->path;
 }
 
+/////////////////////////////////////////////////
+void URI::SetPath(const URIPath &_path)
+{
+  this->dataPtr->path = _path;
+  if (this->dataPtr->pathIncludeAuthority)
+  {
+    auto pathCopy = _path;
+    this->dataPtr->authority = URIAuthority(pathCopy.PopFront());
+  }
+}
+
 /////////////////////////////////////////////////
 const URIPath &URI::Path() const
 {
@@ -973,15 +1027,15 @@ const URIPath &URI::Path() const
 }
 
 /////////////////////////////////////////////////
-URIPath &URI::PathSegments()
+bool URI::PathIncludeAuthority() const
 {
-  return this->dataPtr->path;
+  return this->dataPtr->pathIncludeAuthority;
 }
 
 /////////////////////////////////////////////////
-const URIPath &URI::PathSegments() const
+void URI::SetPathIncludeAuthority(bool _include)
 {
-  return this->dataPtr->path;
+  this->dataPtr->pathIncludeAuthority = _include;
 }
 
 /////////////////////////////////////////////////
@@ -1185,6 +1239,9 @@ bool URI::Parse(const std::string &_str)
   localPath = str.substr(0, pathEndPos);
   str.erase(0, pathEndPos);
 
+  if (this->dataPtr->pathIncludeAuthority && !localAuthority.empty())
+    localPath = localAuthority + localPath;
+
   size_t queryStartPos = str.find("?");
   if (queryStartPos != std::string::npos)
   {
diff --git a/src/URI_TEST.cc b/src/URI_TEST.cc
index ead768a..15f2856 100644
--- a/src/URI_TEST.cc
+++ b/src/URI_TEST.cc
@@ -73,6 +73,9 @@ TEST(URITEST, URIPath)
   EXPECT_EQ(path6.Str(), "/");
   EXPECT_TRUE(path6.IsAbsolute());
 
+  EXPECT_TRUE(path6.PopFront().empty());
+  EXPECT_TRUE(path6.PopBack().empty());
+
   URIPath path7;
   path7.PushFront("/abs");
   EXPECT_EQ(path7.Str(), "/abs");
@@ -116,6 +119,11 @@ TEST(URITEST, URIPath)
   EXPECT_EQ(path7.Str(), "/abs6%2Fabs5/abs4/abs3/abs2/abs");
   EXPECT_TRUE(path7.IsAbsolute());
 
+  EXPECT_EQ("abs6%2Fabs5", path7.PopFront());
+  EXPECT_EQ("/abs4/abs3/abs2/abs", path7.Str());
+  EXPECT_EQ("abs", path7.PopBack());
+  EXPECT_EQ("/abs4/abs3/abs2", path7.Str());
+
   URIPath path8;
   path8.PushBack("/abs");
   EXPECT_EQ(path8.Str(), "/abs");
@@ -454,53 +462,53 @@ TEST(URITEST, Path)
   URI uri;
   uri.SetScheme("data");
 
-  uri.PathSegments() = uri.PathSegments() / "world";
+  uri.SetPath(URIPath("world"));
   EXPECT_EQ(uri.Str(), "data:world");
 
-  uri.PathSegments() /= "default";
+  uri.SetPath(URIPath("world/default"));
   EXPECT_EQ(uri.Str(), "data:world/default");
 
   EXPECT_TRUE(uri.Parse("file:///var/run/test"));
 
   EXPECT_TRUE(uri.Parse("file:/var/run/test"));
   EXPECT_EQ(uri.Str(), "file:/var/run/test");
-  EXPECT_TRUE(uri.PathSegments().IsAbsolute());
+  EXPECT_TRUE(uri.Path().IsAbsolute());
 
   EXPECT_TRUE(uri.Parse("file://var/run/test"));
   EXPECT_EQ(uri.Str(), "file://var/run/test");
   EXPECT_EQ(uri.Authority().Str(), "//var");
-  EXPECT_EQ(uri.PathSegments().Str(), "/run/test");
-  EXPECT_TRUE(uri.PathSegments().IsAbsolute());
+  EXPECT_EQ(uri.Path().Str(), "/var/run/test");
+  EXPECT_TRUE(uri.Path().IsAbsolute());
 
   EXPECT_TRUE(uri.Parse("file://test%20space"));
   EXPECT_EQ(uri.Authority().Str(), "//test%20space");
-  EXPECT_TRUE(uri.PathSegments().Str().empty());
+  EXPECT_EQ("/test%20space", uri.Path().Str());
 
   EXPECT_TRUE(uri.Parse("file:///abs/path/test"));
   EXPECT_EQ(uri.Str(), "file:///abs/path/test");
-  EXPECT_EQ(uri.PathSegments().Str(), "/abs/path/test");
-  EXPECT_TRUE(uri.PathSegments().IsAbsolute());
+  EXPECT_EQ(uri.Path().Str(), "/abs/path/test");
+  EXPECT_TRUE(uri.Path().IsAbsolute());
 
   uri.Parse("file:/var/run/test");
   EXPECT_EQ(uri.Str(), "file:/var/run/test");
-  EXPECT_EQ(uri.PathSegments().Str(), "/var/run/test");
-  EXPECT_TRUE(uri.PathSegments().IsAbsolute());
+  EXPECT_EQ(uri.Path().Str(), "/var/run/test");
+  EXPECT_TRUE(uri.Path().IsAbsolute());
 
   EXPECT_FALSE(uri.Parse("file://test+space"));
 
   EXPECT_TRUE(uri.Parse("file:/test+space"));
   EXPECT_EQ(uri.Str(), "file:/test+space");
-  EXPECT_EQ(uri.PathSegments().Str(), "/test+space");
-  EXPECT_TRUE(uri.PathSegments().IsAbsolute());
+  EXPECT_EQ(uri.Path().Str(), "/test+space");
+  EXPECT_TRUE(uri.Path().IsAbsolute());
 
   EXPECT_TRUE(uri.Parse("file:/test%20space"));
   EXPECT_EQ(uri.Str(), "file:/test%20space");
-  EXPECT_EQ(uri.PathSegments().Str(), "/test%20space");
-  EXPECT_TRUE(uri.PathSegments().IsAbsolute());
+  EXPECT_EQ(uri.Path().Str(), "/test%20space");
+  EXPECT_TRUE(uri.Path().IsAbsolute());
 
   uri.Parse("file://C:/Users");
   EXPECT_EQ(uri.Str(), "file:C:/Users");
-  EXPECT_TRUE(uri.PathSegments().IsAbsolute());
+  EXPECT_TRUE(uri.Path().IsAbsolute());
 }
 
 /////////////////////////////////////////////////
@@ -508,13 +516,16 @@ TEST(URITEST, PathCopy)
 {
   URI uri;
   uri.SetScheme("data");
-  uri.PathSegments().PushFront("world");
+
+  auto path = uri.Path();
+  path.PushFront("world");
+  uri.SetPath(path);
 
   const auto uriCopy(uri);
-  const auto pathCopy(uriCopy.PathSegments() / "default");
+  const auto pathCopy(URIPath(uriCopy.Path().Str() + "/default"));
 
-  EXPECT_NE(uri.PathSegments().Str(), pathCopy.Str());
-  EXPECT_EQ(uri.PathSegments().Str(), "world");
+  EXPECT_NE(uri.Path().Str(), pathCopy.Str());
+  EXPECT_EQ(uri.Path().Str(), "world");
   EXPECT_EQ(pathCopy.Str(), "world/default");
 }
 
@@ -527,15 +538,15 @@ TEST(URITEST, Query)
   uri.Query().Insert("p", "v");
   EXPECT_EQ(uri.Str(), "data:?p=v");
 
-  uri.PathSegments().PushFront("default");
+  uri.Path().PushFront("default");
   EXPECT_EQ(uri.Str(), "data:default?p=v");
 
-  uri.PathSegments().PushFront("world");
+  uri.Path().PushFront("world");
   EXPECT_EQ(uri.Str(), "data:world/default?p=v");
 
   URI uri2 = uri;
 
-  uri.PathSegments().Clear();
+  uri.Path().Clear();
   EXPECT_EQ(uri.Str(), "data:?p=v");
 
   uri.Query().Clear();
@@ -572,15 +583,15 @@ TEST(URITEST, Fragment)
   uri.Fragment() = "#f";
   EXPECT_EQ(uri.Str(), "data:#f");
 
-  uri.PathSegments().PushFront("default");
+  uri.Path().PushFront("default");
   EXPECT_EQ(uri.Str(), "data:default#f");
 
-  uri.PathSegments().PushFront("world");
+  uri.Path().PushFront("world");
   EXPECT_EQ(uri.Str(), "data:world/default#f");
 
   URI uri2 = uri;
 
-  uri.PathSegments().Clear();
+  uri.Path().Clear();
   EXPECT_EQ(uri.Str(), "data:#f");
 
   uri.Fragment().Clear();
@@ -679,7 +690,7 @@ TEST(URITEST, WikipediaTests)
   EXPECT_EQ("www.example.com", uri.Authority().Host());
   EXPECT_EQ(123, *uri.Authority().Port());
   EXPECT_EQ("//[email protected]:123", uri.Authority().Str());
-  EXPECT_EQ("/forum/questions/", uri.PathSegments().Str());
+  EXPECT_EQ("/[email protected]:123/forum/questions/", uri.Path().Str());
   EXPECT_EQ("?tag=networking&order=newest", uri.Query().Str());
   EXPECT_EQ("#top", uri.Fragment().Str());
 
@@ -687,27 +698,27 @@ TEST(URITEST, WikipediaTests)
   EXPECT_EQ("ldap", uri.Scheme());
   EXPECT_EQ("[2001:db8::7]", uri.Authority().Host());
   EXPECT_EQ("//[2001:db8::7]", uri.Authority().Str());
-  EXPECT_EQ("/c=GB", uri.PathSegments().Str());
+  EXPECT_EQ("/[2001:db8::7]/c=GB", uri.Path().Str());
   EXPECT_EQ("?objectClass?one", uri.Query().Str());
 
   EXPECT_TRUE(uri.Parse("mailto:[email protected]"));
   EXPECT_EQ("mailto", uri.Scheme());
   EXPECT_TRUE(uri.Authority().Str().empty());
-  EXPECT_EQ("[email protected]", uri.PathSegments().Str());
+  EXPECT_EQ("[email protected]", uri.Path().Str());
   EXPECT_TRUE(uri.Query().Str().empty());
   EXPECT_TRUE(uri.Fragment().Str().empty());
 
   EXPECT_TRUE(uri.Parse("news:comp.infosystems.www.servers.unix"));
   EXPECT_EQ("news", uri.Scheme());
   EXPECT_TRUE(uri.Authority().Str().empty());
-  EXPECT_EQ("comp.infosystems.www.servers.unix", uri.PathSegments().Str());
+  EXPECT_EQ("comp.infosystems.www.servers.unix", uri.Path().Str());
   EXPECT_TRUE(uri.Query().Str().empty());
   EXPECT_TRUE(uri.Fragment().Str().empty());
 
   EXPECT_TRUE(uri.Parse("tel:+1-816-555-1212"));
   EXPECT_EQ("tel", uri.Scheme());
   EXPECT_TRUE(uri.Authority().Str().empty());
-  EXPECT_EQ("+1-816-555-1212", uri.PathSegments().Str());
+  EXPECT_EQ("+1-816-555-1212", uri.Path().Str());
   EXPECT_TRUE(uri.Query().Str().empty());
   EXPECT_TRUE(uri.Fragment().Str().empty());
 
@@ -716,13 +727,13 @@ TEST(URITEST, WikipediaTests)
   EXPECT_TRUE(uri.Authority().UserInfo().empty());
   EXPECT_EQ("192.0.2.16", uri.Authority().Host());
   EXPECT_EQ(80, uri.Authority().Port());
-  EXPECT_EQ("/", uri.PathSegments().Str());
+  EXPECT_EQ("/192.0.2.16:80/", uri.Path().Str());
 
   EXPECT_TRUE(uri.Parse("urn:oasis:names:specification:docbook:dtd:xml:4.1.2"));
   EXPECT_EQ("urn", uri.Scheme());
   EXPECT_TRUE(uri.Authority().Str().empty());
   EXPECT_EQ("oasis:names:specification:docbook:dtd:xml:4.1.2",
-      uri.PathSegments().Str());
+      uri.Path().Str());
   EXPECT_TRUE(uri.Query().Str().empty());
   EXPECT_TRUE(uri.Fragment().Str().empty());
 }
@@ -765,16 +776,16 @@ TEST(URITEST, File)
 {
   URI uri;
   EXPECT_TRUE(uri.Parse("file:relative/path"));
-  EXPECT_EQ("relative/path", uri.PathSegments().Str());
+  EXPECT_EQ("relative/path", uri.Path().Str());
   EXPECT_FALSE(uri.Authority().EmptyHostValid());
 
   EXPECT_TRUE(uri.Parse("file:/abs/path"));
-  EXPECT_EQ("/abs/path", uri.PathSegments().Str());
+  EXPECT_EQ("/abs/path", uri.Path().Str());
   EXPECT_FALSE(uri.Authority().EmptyHostValid());
 
   // Empty host is valid for file: scheme
   EXPECT_TRUE(uri.Parse("file:///abs/path"));
-  EXPECT_EQ("/abs/path", uri.PathSegments().Str());
+  EXPECT_EQ("/abs/path", uri.Path().Str());
   EXPECT_TRUE(uri.Authority().EmptyHostValid());
 }
 
@@ -787,6 +798,54 @@ TEST(URITEST, WinPath)
   EXPECT_EQ("file:D:/my/test/dir/world.sdf", uri.Str());
 }
 
+//////////////////////////////////////////////////
+TEST(URITEST, PathIncludeAuthority)
+{
+  {
+    // Includes by default
+    URI uri("https://[email protected]:123/forum/questions/");
+
+    EXPECT_EQ("john.doe", uri.Authority().UserInfo());
+    EXPECT_EQ("www.example.com", uri.Authority().Host());
+    EXPECT_EQ(123, *uri.Authority().Port());
+    EXPECT_EQ("//[email protected]:123", uri.Authority().Str());
+
+    EXPECT_EQ("/[email protected]:123/forum/questions/", uri.Path().Str());
+
+    // Modifyng authority also updates path
+    uri.SetAuthority(URIAuthority("//new_authority.com"));
+
+    EXPECT_TRUE(uri.Authority().UserInfo().empty());
+    EXPECT_EQ("new_authority.com", uri.Authority().Host());
+    EXPECT_FALSE(uri.Authority().Port());
+    EXPECT_EQ("//new_authority.com", uri.Authority().Str());
+
+    EXPECT_EQ("/new_authority.com/forum/questions/", uri.Path().Str());
+
+    // Modifyng path also updates authority
+    uri.SetPath(URIPath("/newest_authority.com/another/path"));
+
+    EXPECT_TRUE(uri.Authority().UserInfo().empty());
+    EXPECT_EQ("newest_authority.com", uri.Authority().Host());
+    EXPECT_FALSE(uri.Authority().Port());
+    EXPECT_EQ("//newest_authority.com", uri.Authority().Str());
+
+    EXPECT_EQ("/newest_authority.com/forum/questions/", uri.Path().Str());
+  }
+
+  {
+    // Not including
+    URI uri("https://[email protected]:123/forum/questions/", false);
+
+    EXPECT_EQ("john.doe", uri.Authority().UserInfo());
+    EXPECT_EQ("www.example.com", uri.Authority().Host());
+    EXPECT_EQ(123, *uri.Authority().Port());
+    EXPECT_EQ("//[email protected]:123", uri.Authority().Str());
+
+    EXPECT_EQ("/forum/questions/", uri.Path().Str());
+  }
+}
+
 //////////////////////////////////////////////////
 TEST(URITEST, Resource)
 {
@@ -798,31 +857,70 @@ TEST(URITEST, Resource)
     EXPECT_EQ("//model_name", uri.Authority().Str());
     EXPECT_EQ("model_name", uri.Authority().Host());
     EXPECT_TRUE(uri.Authority().UserInfo().empty());
-    EXPECT_EQ("/meshes/mesh.dae", uri.PathSegments().Str());
+    EXPECT_EQ("/model_name/meshes/mesh.dae", uri.Path().Str());
     EXPECT_TRUE(uri.Query().Str().empty());
     EXPECT_TRUE(uri.Fragment().Str().empty());
   }
 
   {
     URI uri;
+    uri.SetPathIncludeAuthority(false);
+    EXPECT_TRUE(uri.Parse("model://model_name/meshes/mesh.dae"));
+    EXPECT_EQ("model", uri.Scheme());
+    EXPECT_EQ("//model_name", uri.Authority().Str());
+    EXPECT_EQ("model_name", uri.Authority().Host());
+    EXPECT_TRUE(uri.Authority().UserInfo().empty());
+    EXPECT_EQ("/meshes/mesh.dae", uri.Path().Str());
+    EXPECT_TRUE(uri.Query().Str().empty());
+    EXPECT_TRUE(uri.Fragment().Str().empty());
+  }
+
+  {
+    URI uri;
+    EXPECT_TRUE(uri.Parse("model://model_name"));
+    EXPECT_EQ("model", uri.Scheme());
+    EXPECT_EQ("//model_name", uri.Authority().Str());
+    EXPECT_EQ("model_name", uri.Authority().Host());
+    EXPECT_TRUE(uri.Authority().UserInfo().empty());
+    EXPECT_EQ("/model_name", uri.Path().Str());
+    EXPECT_TRUE(uri.Query().Str().empty());
+    EXPECT_TRUE(uri.Fragment().Str().empty());
+  }
+
+  {
+    URI uri;
+    uri.SetPathIncludeAuthority(false);
     EXPECT_TRUE(uri.Parse("model://model_name"));
     EXPECT_EQ("model", uri.Scheme());
     EXPECT_EQ("//model_name", uri.Authority().Str());
     EXPECT_EQ("model_name", uri.Authority().Host());
     EXPECT_TRUE(uri.Authority().UserInfo().empty());
-    EXPECT_TRUE(uri.PathSegments().Str().empty());
+    EXPECT_TRUE(uri.Path().Str().empty());
+    EXPECT_TRUE(uri.Query().Str().empty());
+    EXPECT_TRUE(uri.Fragment().Str().empty());
+  }
+
+  {
+    URI uri;
+    EXPECT_TRUE(uri.Parse("package://package_name/models/model"));
+    EXPECT_EQ("package", uri.Scheme());
+    EXPECT_EQ("//package_name", uri.Authority().Str());
+    EXPECT_EQ("package_name", uri.Authority().Host());
+    EXPECT_TRUE(uri.Authority().UserInfo().empty());
+    EXPECT_EQ("/package_name/models/model", uri.Path().Str());
     EXPECT_TRUE(uri.Query().Str().empty());
     EXPECT_TRUE(uri.Fragment().Str().empty());
   }
 
   {
     URI uri;
+    uri.SetPathIncludeAuthority(false);
     EXPECT_TRUE(uri.Parse("package://package_name/models/model"));
     EXPECT_EQ("package", uri.Scheme());
     EXPECT_EQ("//package_name", uri.Authority().Str());
     EXPECT_EQ("package_name", uri.Authority().Host());
     EXPECT_TRUE(uri.Authority().UserInfo().empty());
-    EXPECT_EQ("/models/model", uri.PathSegments().Str());
+    EXPECT_EQ("/models/model", uri.Path().Str());
     EXPECT_TRUE(uri.Query().Str().empty());
     EXPECT_TRUE(uri.Fragment().Str().empty());
   }

It starts to become tricky to keep the authority and the path in sync. I added a SetPath function and deprecated the mutable Path() so that the URI class has control over when the path is mutated, and can update the authority accordingly. That turned out to be a bad idea because the compiler chooses the mutable Path() instead of the const one whenever the URI object isn't itself const, which means downstream users would need to workaround this even for use cases where the const Path() is ok.

So I'm backtracking and will do the first option instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I implemented URI::HasAuthority APIs that let users turn on the new behaviour, but default to the old one. The current state of this branch works with gazebosim/gz-fuel-tools#163. Mind taking a look, @nkoenig ?

Signed-off-by: Louise Poubel <[email protected]>
Signed-off-by: Louise Poubel <[email protected]>
Signed-off-by: Louise Poubel <[email protected]>
Copy link
Contributor

@mjcarroll mjcarroll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider the use of std::optional

src/URI.cc Outdated Show resolved Hide resolved
include/ignition/common/SystemPaths.hh Show resolved Hide resolved
src/URI.cc Outdated Show resolved Hide resolved
src/URI.cc Outdated Show resolved Hide resolved
Signed-off-by: Louise Poubel <[email protected]>
Copy link
Contributor

@mjcarroll mjcarroll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few Q's about std::optional usage.

src/URI.cc Outdated Show resolved Hide resolved
src/URI.cc Outdated Show resolved Hide resolved
src/URI.cc Outdated Show resolved Hide resolved
Signed-off-by: Louise Poubel <[email protected]>
Signed-off-by: Louise Poubel <[email protected]>
@chapulina
Copy link
Contributor Author

Ci is all 🟢 !

Copy link
Contributor

@mjcarroll mjcarroll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@mjcarroll mjcarroll merged commit df047cb into main Feb 20, 2021
@mjcarroll mjcarroll deleted the chapulina/4/URI branch February 20, 2021 00:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants