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:
parent
624d81ed0f
commit
ae575b0c08
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
25
sdk/core/azure-core/inc/azure/core/case_insensitive_map.hpp
Normal file
25
sdk/core/azure-core/inc/azure/core/case_insensitive_map.hpp
Normal 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
|
||||
@ -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.
|
||||
|
||||
@ -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;
|
||||
};
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}
|
||||
|
||||
33
sdk/core/azure-core/test/ut/case_insensitive_map.cpp
Normal file
33
sdk/core/azure-core/test/ut/case_insensitive_map.cpp
Normal 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");
|
||||
}
|
||||
@ -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++;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 {
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user