diff options
author | Richard Walters <rwalters@digitalstirling.com> | 2018-06-30 15:26:33 -0700 |
---|---|---|
committer | Richard Walters <rwalters@digitalstirling.com> | 2018-06-30 15:26:33 -0700 |
commit | a84a28c61ff44f714edf31e77500d796e3bd4ce4 (patch) | |
tree | c09edfc1355cb036e98ce1aaf25c8e65b2d53c44 | |
parent | 9ead8935e92119369f7d82c1514d390b18822c90 (diff) |
Kick off Uri component
* Can now parse URIs from strings.
* This supports scheme, host, and path.
* Path separator defaults to "/" but may be customized.
-rw-r--r-- | include/Uri/Uri.hpp | 61 | ||||
-rw-r--r-- | src/Uri.cpp | 78 | ||||
-rw-r--r-- | test/src/UriTests.cpp | 80 |
3 files changed, 217 insertions, 2 deletions
diff --git a/include/Uri/Uri.hpp b/include/Uri/Uri.hpp index b5f85c2..6103bff 100644 --- a/include/Uri/Uri.hpp +++ b/include/Uri/Uri.hpp @@ -10,6 +10,8 @@ */ #include <memory> +#include <string> +#include <vector> namespace Uri { @@ -33,6 +35,65 @@ namespace Uri { */ Uri(); + /** + * This method sets the character or character sequence + * that should be interpreted as a path delimiter. + * + * @param[in] newPathDelimiter + * This is the character or character sequence + * that should be interpreted as a path delimiter. + */ + void SetPathDelimiter(const std::string& newPathDelimiter); + + /** + * This method builds the URI from the elements parsed + * from the given string rendering of a URI. + * + * @param[in] uriString + * This is the string rendering of the URI to parse. + * + * @return + * An indication of whether or not the URI was + * parsed successfully is returned. + */ + bool ParseFromString(const std::string& uriString); + + /** + * This method returns the "scheme" element of the URI. + * + * @return + * The "scheme" element of the URI is returned. + * + * @retval "" + * This is returned if there is no "scheme" element in the URI. + */ + std::string GetScheme() const; + + /** + * This method returns the "host" element of the URI. + * + * @return + * The "host" element of the URI is returned. + * + * @retval "" + * This is returned if there is no "host" element in the URI. + */ + std::string GetHost() const; + + /** + * This method returns the "path" element of the URI, + * as a sequence of steps. + * + * @note + * If the first step of the path is an empty string, + * then the URI has an absolute path. + * + * @return + * The "path" element of the URI is returned + * as a sequence of steps. + */ + std::vector< std::string > GetPath() const; + // Private properties private: /** diff --git a/src/Uri.cpp b/src/Uri.cpp index d63a5a9..287d3ba 100644 --- a/src/Uri.cpp +++ b/src/Uri.cpp @@ -6,13 +6,36 @@ * © 2018 by Richard Walters */ +#include <string> #include <Uri/Uri.hpp> +#include <vector> namespace Uri { /** * This contains the private properties of a Uri instance. */ struct Uri::Impl { + /** + * This is the character or character sequence + * that should be interpreted as a path delimiter. + */ + std::string pathDelimiter = "/"; + + /** + * This is the "scheme" element of the URI. + */ + std::string scheme; + + /** + * This is the "host" element of the URI. + */ + std::string host; + + /** + * This is the "path" element of the URI, + * as a sequence of steps. + */ + std::vector< std::string > path; }; Uri::~Uri() = default; @@ -22,4 +45,59 @@ namespace Uri { { } + void Uri::SetPathDelimiter(const std::string& newPathDelimiter) { + impl_->pathDelimiter = newPathDelimiter; + } + + bool Uri::ParseFromString(const std::string& uriString) { + // First parse the scheme. + const auto schemeEnd = uriString.find(':'); + impl_->scheme = uriString.substr(0, schemeEnd); + auto rest = uriString.substr(schemeEnd + 1); + + // Next parse the host. + if (rest.substr(0, 2) == "//") { + const auto authorityEnd = rest.find(impl_->pathDelimiter, 2); + impl_->host = rest.substr(2, authorityEnd - 2); + rest = rest.substr(authorityEnd); + } else { + impl_->host.clear(); + } + + // Finally, parse the path. + impl_->path.clear(); + if (rest == impl_->pathDelimiter) { + // Special case of a path that is empty but needs a single + // empty-string element to indicate that it is absolute. + impl_->path.push_back(""); + } else if (!rest.empty()) { + for(;;) { + auto pathDelimiter = rest.find(impl_->pathDelimiter); + if (pathDelimiter == std::string::npos) { + impl_->path.push_back(rest); + break; + } else { + impl_->path.emplace_back( + rest.begin(), + rest.begin() + pathDelimiter + ); + rest = rest.substr(pathDelimiter + impl_->pathDelimiter.length()); + } + } + } + return true; + } + + std::string Uri::GetScheme() const { + return impl_->scheme; + } + + std::string Uri::GetHost() const { + return impl_->host; + } + + std::vector< std::string > Uri::GetPath() const { + return impl_->path; + } + } diff --git a/test/src/UriTests.cpp b/test/src/UriTests.cpp index 3cb5336..73848a9 100644 --- a/test/src/UriTests.cpp +++ b/test/src/UriTests.cpp @@ -7,9 +7,85 @@ */ #include <gtest/gtest.h> +#include <stddef.h> #include <Uri/Uri.hpp> -TEST(UriTests, Placeholder) { +TEST(UriTests, ParseFromStringUrl) { Uri::Uri uri; - ASSERT_TRUE(true); + ASSERT_TRUE(uri.ParseFromString("http://www.example.com/foo/bar")); + ASSERT_EQ("http", uri.GetScheme()); + ASSERT_EQ("www.example.com", uri.GetHost()); + ASSERT_EQ( + (std::vector< std::string >{ + "", + "foo", + "bar", + }), + uri.GetPath() + ); +} + +TEST(UriTests, ParseFromStringUrnDefaultPathDelimiter) { + Uri::Uri uri; + ASSERT_TRUE(uri.ParseFromString("urn:book:fantasy:Hobbit")); + ASSERT_EQ("urn", uri.GetScheme()); + ASSERT_EQ("", uri.GetHost()); + ASSERT_EQ( + (std::vector< std::string >{ + "book:fantasy:Hobbit", + }), + uri.GetPath() + ); +} + +TEST(UriTests, ParseFromStringUrnSingleCharacterPathDelimiter) { + Uri::Uri uri; + uri.SetPathDelimiter(":"); + ASSERT_TRUE(uri.ParseFromString("urn:book:fantasy:Hobbit")); + ASSERT_EQ("urn", uri.GetScheme()); + ASSERT_EQ("", uri.GetHost()); + ASSERT_EQ( + (std::vector< std::string >{ + "book", + "fantasy", + "Hobbit", + }), + uri.GetPath() + ); +} + +TEST(UriTests, ParseFromStringUrnMultiCharacterPathDelimiter) { + Uri::Uri uri; + uri.SetPathDelimiter("/-"); + ASSERT_TRUE(uri.ParseFromString("urn:bo-/ok/-fant/asy/-Hob-bit")); + ASSERT_EQ("urn", uri.GetScheme()); + ASSERT_EQ("", uri.GetHost()); + ASSERT_EQ( + (std::vector< std::string >{ + "bo-/ok", + "fant/asy", + "Hob-bit", + }), + uri.GetPath() + ); +} + +TEST(UriTests, ParseFromStringPathCornerCases) { + struct TestVector { + std::string pathIn; + std::vector< std::string > pathOut; + }; + const std::vector< TestVector > testVectors{ + {"", {}}, + {"/", {""}}, + {"/foo", {"", "foo"} }, + {"foo/", {"foo", ""} }, + }; + size_t index = 0; + for (const auto& testVector : testVectors) { + Uri::Uri uri; + ASSERT_TRUE(uri.ParseFromString(testVector.pathIn)) << index; + ASSERT_EQ(testVector.pathOut, uri.GetPath()) << index; + ++index; + } } |