CaseInsensitiveMap (#1731)

* Add CaseInsensitiveMap<T>

* Drop unneccessary namespace qualification

* Comment working: more accurate description

* GCC and Clang fix (typename specification) + doxy comments for template parameters

* Remove Allocator template parameter - we can add it later if we need it, currently no need to commit to having it there

* Drop <T> template parameter. We can add it later with default value of std::string without breaking change

* Unit test

Co-authored-by: Anton Kolesnyk <antkmsft@users.noreply.github.com>
This commit is contained in:
Anton Kolesnyk 2021-02-26 08:38:01 -08:00 committed by GitHub
parent 624d81ed0f
commit ae575b0c08
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 105 additions and 51 deletions

View File

@ -9,9 +9,10 @@
- Removed `TransportKind` enum from `Azure::Core::Http`.
- Renamed `NoRevoke` to `EnableCertificateRevocationListCheck` for `Azure::Core::Http::CurlTransportSSLOptions`.
- Renamed `GetString()` to `ToString()` in `Azure::Core::DateTime`.
- Renamed `GetUuidString()` tp `ToString()` in `Azure::Core::Uuid`.
- Renamed `GetUuidString()` to `ToString()` in `Azure::Core::Uuid`.
- Moved `NullBodyStream` to internal usage only. It is not meant for public use.
- Removed `LimitBodyStream`.
- Introduced `Azure::Core::CaseInsensitiveMap` which is now used to store headers in `Azure::Core::Http::Request` and `Azure::Core::Http::RawResponse`.
### Bug Fixes

View File

@ -60,6 +60,7 @@ set(
inc/azure/core/internal/strings.hpp
inc/azure/core/logging/logging.hpp
inc/azure/core/base64.hpp
inc/azure/core/case_insensitive_map.hpp
inc/azure/core/context.hpp
inc/azure/core/credentials.hpp
inc/azure/core/datetime.hpp

View File

@ -0,0 +1,25 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
* @brief A `map<string, string>` with case-insensitive key comparison.
*/
#pragma once
#include "azure/core/internal/strings.hpp"
#include <map>
#include <string>
namespace Azure { namespace Core {
/**
* @brief A type alias of `std::map<std::string, std::string>` with case-insensitive key
* comparison.
*/
using CaseInsensitiveMap
= std::map<std::string, std::string, Internal::Strings::CaseInsensitiveComparator>;
}} // namespace Azure::Core

View File

@ -8,6 +8,7 @@
#pragma once
#include "azure/core/case_insensitive_map.hpp"
#include "azure/core/exception.hpp"
#include "azure/core/http/body_stream.hpp"
#include "azure/core/internal/contract.hpp"
@ -45,7 +46,7 @@ namespace Azure { namespace Core { namespace Http {
* @throw if \p headerName is invalid.
*/
void InsertHeaderWithValidation(
std::map<std::string, std::string>& headers,
CaseInsensitiveMap& headers,
std::string const& headerName,
std::string const& headerValue);
} // namespace Details
@ -409,11 +410,11 @@ namespace Azure { namespace Core { namespace Http {
uint16_t GetPort() const { return m_port; }
/**
* @brief Provides a copy to the list of query parameters from the URL.
* @brief Get a copy of the list of query parameters from the URL.
*
* @remark The query parameters are URL-encoded.
*
* @return const std::map<std::string, std::string>&
* @return A copy of the query parameters map.
*/
std::map<std::string, std::string> GetQueryParameters() const
{
@ -421,16 +422,16 @@ namespace Azure { namespace Core { namespace Http {
}
/**
* @brief Gets the path and query parameters.
* @brief Get the path and query parameters.
*
* @return std::string The string is URL encoded.
* @return Relative URL with URL-encoded query parameters.
*/
std::string GetRelativeUrl() const;
/**
* @brief Gets Scheme, host, path and query parameters.
* @brief Get Scheme, host, path and query parameters.
*
* @return std::string The string is URL encoded.
* @return Absolute URL with URL-encoded query parameters.
*/
std::string GetAbsoluteUrl() const;
};
@ -449,8 +450,8 @@ namespace Azure { namespace Core { namespace Http {
private:
HttpMethod m_method;
Url m_url;
std::map<std::string, std::string> m_headers;
std::map<std::string, std::string> m_retryHeaders;
CaseInsensitiveMap m_headers;
CaseInsensitiveMap m_retryHeaders;
BodyStream* m_bodyStream;
@ -542,7 +543,7 @@ namespace Azure { namespace Core { namespace Http {
/**
* @brief Get HTTP headers.
*/
std::map<std::string, std::string> GetHeaders() const;
CaseInsensitiveMap GetHeaders() const;
/**
* @brief Get HTTP body as #Azure::Core::Http::BodyStream.
@ -593,7 +594,7 @@ namespace Azure { namespace Core { namespace Http {
int32_t m_minorVersion;
HttpStatusCode m_statusCode;
std::string m_reasonPhrase;
std::map<std::string, std::string> m_headers;
CaseInsensitiveMap m_headers;
std::unique_ptr<BodyStream> m_bodyStream;
std::vector<uint8_t> m_body;
@ -727,7 +728,7 @@ namespace Azure { namespace Core { namespace Http {
/**
* @brief Get HTTP response headers.
*/
std::map<std::string, std::string> const& GetHeaders() const;
CaseInsensitiveMap const& GetHeaders() const;
/**
* @brief Get HTTP response body as #Azure::Core::Http::BodyStream.

View File

@ -8,6 +8,7 @@
#pragma once
#include "azure/core/case_insensitive_map.hpp"
#include "azure/core/context.hpp"
#include "azure/core/credentials.hpp"
#include "azure/core/http/http.hpp"
@ -388,7 +389,7 @@ namespace Azure { namespace Core { namespace Http {
*/
struct ValuePolicyOptions
{
std::map<std::string, std::string> HeaderValues;
CaseInsensitiveMap HeaderValues;
std::map<std::string, std::string> QueryValues;
};

View File

@ -8,6 +8,7 @@
*/
#pragma once
#include <algorithm>
#include <string>
namespace Azure { namespace Core { namespace Internal { namespace Strings {
@ -16,4 +17,15 @@ namespace Azure { namespace Core { namespace Internal { namespace Strings {
std::string const ToLower(const std::string& src) noexcept;
unsigned char ToLower(const unsigned char src) noexcept;
struct CaseInsensitiveComparator
{
bool operator()(const std::string& lhs, const std::string& rhs) const
{
return std::lexicographical_compare(
lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), [](char c1, char c2) {
return ToLower(c1) < ToLower(c2);
});
}
};
}}}} // namespace Azure::Core::Internal::Strings

View File

@ -10,7 +10,7 @@ using namespace Azure::Core::Http;
using namespace Azure::Core::Internal::Http;
void Azure::Core::Http::Details::InsertHeaderWithValidation(
std::map<std::string, std::string>& headers,
Azure::Core::CaseInsensitiveMap& headers,
std::string const& headerName,
std::string const& headerValue)
{

View File

@ -15,10 +15,7 @@ HttpStatusCode RawResponse::GetStatusCode() const { return m_statusCode; }
std::string const& RawResponse::GetReasonPhrase() const { return m_reasonPhrase; }
std::map<std::string, std::string> const& RawResponse::GetHeaders() const
{
return this->m_headers;
}
Azure::Core::CaseInsensitiveMap const& RawResponse::GetHeaders() const { return this->m_headers; }
void RawResponse::AddHeader(uint8_t const* const first, uint8_t const* const last)
{

View File

@ -13,9 +13,9 @@ using namespace Azure::Core::Http;
namespace {
// returns left map plus all items in right
// when duplicates, left items are preferred
static std::map<std::string, std::string> MergeMaps(
std::map<std::string, std::string> left,
std::map<std::string, std::string> const& right)
static Azure::Core::CaseInsensitiveMap MergeMaps(
Azure::Core::CaseInsensitiveMap left,
Azure::Core::CaseInsensitiveMap const& right)
{
left.insert(right.begin(), right.end());
return left;
@ -51,7 +51,7 @@ void Request::StartTry()
HttpMethod Request::GetMethod() const { return this->m_method; }
std::map<std::string, std::string> Request::GetHeaders() const
Azure::Core::CaseInsensitiveMap Request::GetHeaders() const
{
// create map with retry headers which are the most important and we don't want
// to override them with any duplicate header

View File

@ -31,6 +31,7 @@ add_executable (
azure-core-test
base64.cpp
bodystream.cpp
case_insensitive_map.cpp
context.cpp
${CURL_CONNECTION_POOL_TESTS}
${CURL_OPTIONS_TESTS}

View File

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include <gtest/gtest.h>
#include <azure/core/case_insensitive_map.hpp>
using namespace Azure::Core;
TEST(CaseInsensitiveMap, Find)
{
CaseInsensitiveMap imap;
imap["Content-Length"] = "X";
auto pos = imap.find("content-length");
EXPECT_NE(pos, imap.end());
EXPECT_EQ(pos->second, "X");
EXPECT_EQ(pos->first, "Content-Length");
}
TEST(CaseInsensitiveMap, Modify)
{
CaseInsensitiveMap imap;
imap["Content-Length"] = "X";
imap["content-length"] = "Y";
auto pos = imap.find("CONTENT-LENGTH");
EXPECT_NE(pos, imap.end());
EXPECT_EQ(pos->second, "Y");
EXPECT_EQ(pos->first, "Content-Length");
}

View File

@ -25,8 +25,7 @@ namespace Azure { namespace Core { namespace Test {
EXPECT_NO_THROW(req.AddHeader(expected.first, expected.second));
EXPECT_PRED2(
[](std::map<std::string, std::string> headers,
std::pair<std::string, std::string> expected) {
[](Azure::Core::CaseInsensitiveMap headers, std::pair<std::string, std::string> expected) {
auto firstHeader = headers.begin();
return firstHeader->first == expected.first && firstHeader->second == expected.second
&& headers.size() == 1;
@ -40,8 +39,7 @@ namespace Azure { namespace Core { namespace Test {
std::pair<std::string, std::string> expectedOverride("valid", "override");
EXPECT_NO_THROW(req.AddHeader(expectedOverride.first, expectedOverride.second));
EXPECT_PRED2(
[](std::map<std::string, std::string> headers,
std::pair<std::string, std::string> expected) {
[](Azure::Core::CaseInsensitiveMap headers, std::pair<std::string, std::string> expected) {
auto firstHeader = headers.begin();
return firstHeader->first == expected.first && firstHeader->second == expected.second
&& headers.size() == 1;
@ -53,8 +51,7 @@ namespace Azure { namespace Core { namespace Test {
std::pair<std::string, std::string> expected2("valid2", "header2");
EXPECT_NO_THROW(req.AddHeader(expected2.first, expected2.second));
EXPECT_PRED2(
[](std::map<std::string, std::string> headers,
std::pair<std::string, std::string> expected) {
[](Azure::Core::CaseInsensitiveMap headers, std::pair<std::string, std::string> expected) {
auto secondHeader = headers.begin();
secondHeader++;
return secondHeader->first == expected.first && secondHeader->second == expected.second
@ -73,8 +70,7 @@ namespace Azure { namespace Core { namespace Test {
EXPECT_NO_THROW(response.AddHeader(expected.first, expected.second));
EXPECT_PRED2(
[](std::map<std::string, std::string> headers,
std::pair<std::string, std::string> expected) {
[](Azure::Core::CaseInsensitiveMap headers, std::pair<std::string, std::string> expected) {
auto firstHeader = headers.begin();
return firstHeader->first == expected.first && firstHeader->second == expected.second
&& headers.size() == 1;
@ -89,8 +85,7 @@ namespace Azure { namespace Core { namespace Test {
std::pair<std::string, std::string> expectedOverride("valid", "override");
EXPECT_NO_THROW(response.AddHeader(expectedOverride.first, expectedOverride.second));
EXPECT_PRED2(
[](std::map<std::string, std::string> headers,
std::pair<std::string, std::string> expected) {
[](Azure::Core::CaseInsensitiveMap headers, std::pair<std::string, std::string> expected) {
auto firstHeader = headers.begin();
return firstHeader->first == expected.first && firstHeader->second == expected.second
&& headers.size() == 1;
@ -102,8 +97,7 @@ namespace Azure { namespace Core { namespace Test {
std::pair<std::string, std::string> expected2("valid2", "header2");
EXPECT_NO_THROW(response.AddHeader(expected2.first, expected2.second));
EXPECT_PRED2(
[](std::map<std::string, std::string> headers,
std::pair<std::string, std::string> expected) {
[](Azure::Core::CaseInsensitiveMap headers, std::pair<std::string, std::string> expected) {
auto secondtHeader = headers.begin();
secondtHeader++;
return secondtHeader->first == expected.first && secondtHeader->second == expected.second
@ -120,8 +114,7 @@ namespace Azure { namespace Core { namespace Test {
// adding header after previous error just happened on add from string
EXPECT_NO_THROW(response.AddHeader("valid3: header3"));
EXPECT_PRED2(
[](std::map<std::string, std::string> headers,
std::pair<std::string, std::string> expected) {
[](Azure::Core::CaseInsensitiveMap headers, std::pair<std::string, std::string> expected) {
auto secondtHeader = headers.begin();
secondtHeader++;
secondtHeader++;

View File

@ -14,7 +14,7 @@ using namespace Azure::Security::KeyVault::Common;
namespace {
inline std::string GetHeaderOrEmptyString(
std::map<std::string, std::string> const& headers,
Azure::Core::CaseInsensitiveMap const& headers,
std::string const& headerName)
{
auto header = headers.find(headerName);

View File

@ -10,8 +10,8 @@
#include <string>
#include <vector>
#include <azure/core/case_insensitive_map.hpp>
#include <azure/core/http/policy.hpp>
#include <azure/core/internal/strings.hpp>
#include "azure/storage/common/constants.hpp"
#include "azure/storage/common/storage_per_retry_policy.hpp"
@ -52,21 +52,10 @@ namespace Azure { namespace Storage {
};
namespace Details {
struct CaseInsensitiveComparator
{
bool operator()(const std::string& lhs, const std::string& rhs) const
{
return std::lexicographical_compare(
lhs.begin(), lhs.end(), rhs.begin(), rhs.end(), [](char c1, char c2) {
return Core::Internal::Strings::ToLower(c1) < Core::Internal::Strings::ToLower(c2);
});
}
};
ContentHash FromBase64String(const std::string& base64String, HashAlgorithm algorithm);
std::string ToBase64String(const ContentHash& hash);
} // namespace Details
using Metadata = std::map<std::string, std::string, Details::CaseInsensitiveComparator>;
using Metadata = Azure::Core::CaseInsensitiveMap;
namespace Details {