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

Add getPath and setPath methods to the Uri class, fix a bug in joinToString #849

Merged
merged 8 commits into from
Apr 6, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Change Log

### ? - ?

##### Additions :tada:

- Added `Uri::getPath` and `Uri::setPath`.

##### Fixes :wrench:

- Fixed a bug in `joinToString` when given a collection containing empty strings.

### v0.34.0 - 2024-04-01

##### Breaking Changes :mega:
Expand Down
20 changes: 20 additions & 0 deletions CesiumUtility/include/CesiumUtility/Uri.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,25 @@ class Uri final {
const std::function<SubstitutionCallbackSignature>& substitutionCallback);

static std::string escape(const std::string& s);

/**
* @brief Gets the path portion of the URI.
j9liu marked this conversation as resolved.
Show resolved Hide resolved
*
* @param uri The URI from which to get the path.
* @return The path, or empty string if the URI could not be parsed.
*/
static std::string getPath(const std::string& uri);

/**
* @brief Sets the path portion of a URI to a new value. The other portions of
* the URI are left unmodified.
j9liu marked this conversation as resolved.
Show resolved Hide resolved
*
* @param uri The URI for which to set the path.
* @param The new path portion of the URI.
* @returns The new URI after setting the path. If the original URI cannot be
* parsed, it is returned unmodified.
*/
static std::string
setPath(const std::string& uri, const std::string& newPath);
};
} // namespace CesiumUtility
27 changes: 9 additions & 18 deletions CesiumUtility/include/CesiumUtility/joinToString.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,17 @@ namespace CesiumUtility {
template <class TIterator>
std::string
joinToString(TIterator begin, TIterator end, const std::string& separator) {
if (begin == end)
return std::string();

std::string first = *begin;

return std::accumulate(
begin,
++begin,
end,
std::string(),
std::move(first),
[&separator](const std::string& acc, const std::string& element) {
if (!acc.empty()) {
return acc + separator + element;
} else {
return element;
}
return acc + separator + element;
});
}

Expand All @@ -41,16 +42,6 @@ joinToString(TIterator begin, TIterator end, const std::string& separator) {
*/
template <class TCollection>
std::string joinToString(TCollection collection, const std::string& separator) {
return std::accumulate(
collection.cbegin(),
collection.cend(),
std::string(),
[&separator](const std::string& acc, const std::string& element) {
if (!acc.empty()) {
return acc + separator + element;
} else {
return element;
}
});
return joinToString(collection.cbegin(), collection.cend(), separator);
}
} // namespace CesiumUtility
104 changes: 104 additions & 0 deletions CesiumUtility/src/Uri.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
#include "CesiumUtility/Uri.h"

#include <CesiumUtility/joinToString.h>

#include <uriparser/Uri.h>

#include <cstdlib>
#include <cstring>
#include <stdexcept>
#include <vector>

namespace CesiumUtility {
std::string Uri::resolve(
Expand Down Expand Up @@ -175,4 +179,104 @@ std::string Uri::escape(const std::string& s) {
result.resize(size_t(pTerminator - result.data()));
return result;
}

std::string Uri::getPath(const std::string& uri) {
UriUriA parsedUri;
if (uriParseSingleUriA(&parsedUri, uri.c_str(), nullptr) != URI_SUCCESS) {
// Could not parse the URI, so return an empty string.
return std::string();
}

// The initial string in this vector can be thought of as the "nothing" before
// the first slash in the path.
std::vector<std::string> parts{std::string()};

UriPathSegmentA* pCurrent = parsedUri.pathHead;
while (pCurrent != nullptr) {
parts.emplace_back(std::string(
pCurrent->text.first,
size_t(pCurrent->text.afterLast - pCurrent->text.first)));
pCurrent = pCurrent->next;
}

uriFreeUriMembersA(&parsedUri);

return joinToString(parts, "/");
}

std::string Uri::setPath(const std::string& uri, const std::string& newPath) {
UriUriA parsedUri;
if (uriParseSingleUriA(&parsedUri, uri.c_str(), nullptr) != URI_SUCCESS) {
// Could not parse the URI, so return an empty string.
return std::string();
}

// Free the existing path. Strangely, uriparser doesn't provide any simple way
// to do this.
UriPathSegmentA* pCurrent = parsedUri.pathHead;
while (pCurrent != nullptr) {
UriPathSegmentA* pNext = pCurrent->next;
free(pCurrent);
pCurrent = pNext;
}

parsedUri.pathHead = nullptr;
parsedUri.pathTail = nullptr;

// Set the new path.
if (!newPath.empty()) {
std::string::size_type startPos = 0;
do {
std::string::size_type nextSlashIndex = newPath.find('/', startPos);

// Skip the initial slash if there is one.
if (nextSlashIndex == 0) {
startPos = 1;
continue;
}

UriPathSegmentA* pSegment =
static_cast<UriPathSegmentA*>(malloc(sizeof(UriPathSegmentA)));
memset(pSegment, 0, sizeof(UriPathSegmentA));

if (parsedUri.pathHead == nullptr) {
parsedUri.pathHead = pSegment;
parsedUri.pathTail = pSegment;
} else {
parsedUri.pathTail->next = pSegment;
parsedUri.pathTail = parsedUri.pathTail->next;
}

pSegment->text.first = newPath.data() + startPos;

if (nextSlashIndex != std::string::npos) {
pSegment->text.afterLast = newPath.data() + nextSlashIndex;
startPos = nextSlashIndex + 1;
} else {
pSegment->text.afterLast = newPath.data() + newPath.size();
startPos = nextSlashIndex;
}
} while (startPos != std::string::npos);
}

int charsRequired;
if (uriToStringCharsRequiredA(&parsedUri, &charsRequired) != URI_SUCCESS) {
uriFreeUriMembersA(&parsedUri);
return uri;
}

std::string result(static_cast<size_t>(charsRequired), ' ');

if (uriToStringA(
const_cast<char*>(result.c_str()),
&parsedUri,
charsRequired + 1,
nullptr) != URI_SUCCESS) {
uriFreeUriMembersA(&parsedUri);
return uri;
}

return result;
}

} // namespace CesiumUtility
22 changes: 22 additions & 0 deletions CesiumUtility/test/TestJoinToString.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include <CesiumUtility/joinToString.h>

#include <catch2/catch.hpp>

using namespace CesiumUtility;

TEST_CASE("joinToString") {
j9liu marked this conversation as resolved.
Show resolved Hide resolved
CHECK(
joinToString(std::vector<std::string>{"test", "this"}, "--") ==
"test--this");
CHECK(
joinToString(std::vector<std::string>{"test", "this", "thing"}, " ") ==
"test this thing");
CHECK(
joinToString(std::vector<std::string>{"test", "this", "thing"}, "") ==
"testthisthing");
CHECK(joinToString(std::vector<std::string>{"test"}, "--") == "test");
CHECK(joinToString(std::vector<std::string>{""}, "--") == "");
CHECK(joinToString(std::vector<std::string>{"", "aa", ""}, "--") == "--aa--");
CHECK(joinToString(std::vector<std::string>{"", ""}, "--") == "--");
CHECK(joinToString(std::vector<std::string>{}, "--") == "");
}
58 changes: 58 additions & 0 deletions CesiumUtility/test/TestUri.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#include <CesiumUtility/Uri.h>

#include <catch2/catch.hpp>

using namespace CesiumUtility;

TEST_CASE("Uri::getPath") {
CHECK(Uri::getPath("https://example.com/") == "/");
CHECK(Uri::getPath("https://example.com/foo/bar") == "/foo/bar");
CHECK(Uri::getPath("https://example.com/foo/bar/") == "/foo/bar/");
CHECK(Uri::getPath("https://example.com/?some=parameter") == "/");
CHECK(
Uri::getPath("https://example.com/foo/bar?some=parameter") == "/foo/bar");
CHECK(
Uri::getPath("https://example.com/foo/bar/?some=parameter") ==
"/foo/bar/");
CHECK(Uri::getPath("https://example.com") == "");
CHECK(Uri::getPath("https://example.com?some=parameter") == "");
CHECK(Uri::getPath("not a valid uri") == "");
}

TEST_CASE("Uri::setPath") {
CHECK(Uri::setPath("https://example.com", "") == "https://example.com");
CHECK(
Uri::setPath("https://example.com?some=parameter", "") ==
"https://example.com?some=parameter");
CHECK(Uri::setPath("https://example.com", "/") == "https://example.com/");
CHECK(
Uri::setPath("https://example.com?some=parameter", "/") ==
"https://example.com/?some=parameter");
CHECK(
Uri::setPath("https://example.com/foo?some=parameter", "/bar") ==
"https://example.com/bar?some=parameter");
CHECK(
Uri::setPath("https://example.com/foo", "/bar") ==
"https://example.com/bar");
CHECK(
Uri::setPath("https://example.com/foo?some=parameter", "/bar") ==
"https://example.com/bar?some=parameter");
CHECK(
Uri::setPath("https://example.com/foo/", "/bar") ==
"https://example.com/bar");
CHECK(
Uri::setPath("https://example.com/foo/?some=parameter", "/bar") ==
"https://example.com/bar?some=parameter");
CHECK(
Uri::setPath("https://example.com/foo", "/bar/") ==
"https://example.com/bar/");
CHECK(
Uri::setPath("https://example.com/foo?some=parameter", "/bar/") ==
"https://example.com/bar/?some=parameter");
CHECK(
Uri::setPath("https://example.com/foo/bar", "/foo/bar") ==
"https://example.com/foo/bar");
CHECK(
Uri::setPath("https://example.com/foo/bar?some=parameter", "/foo/bar") ==
"https://example.com/foo/bar?some=parameter");
}
Loading