Add support for HTTP Etag to Azure::Core (#1428)

* Add ETag class
This commit is contained in:
Rick Winter 2021-01-26 12:04:34 -08:00 committed by GitHub
parent 153a6dd8b8
commit 50a4186d96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 439 additions and 2 deletions

View File

@ -2,6 +2,10 @@
## 1.0.0-beta.5 (Unreleased)
### New Features
- Added support for HTTP validators `ETag`.
### Breaking Changes
- Make `ToLower` and `LocaleInvariantCaseInsensitiveEqual` internal by moving them from `Azure::Core::Strings` to `Azure::Core::Internal::Strings`.

View File

@ -60,6 +60,7 @@ set(
inc/azure/core/credentials.hpp
inc/azure/core/datetime.hpp
inc/azure/core/dll_import_export.hpp
inc/azure/core/etag.hpp
inc/azure/core/exception.hpp
inc/azure/core/nullable.hpp
inc/azure/core/operation.hpp

View File

@ -15,6 +15,7 @@
#include "azure/core/credentials.hpp"
#include "azure/core/datetime.hpp"
#include "azure/core/dll_import_export.hpp"
#include "azure/core/etag.hpp"
#include "azure/core/nullable.hpp"
#include "azure/core/response.hpp"
#include "azure/core/uuid.hpp"

View File

@ -0,0 +1,195 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @file
* @brief Define ETag.
*/
#pragma once
#include "azure/core/nullable.hpp"
#include <string>
namespace Azure { namespace Core {
/**
* @brief Represents an HTTP validator.
*/
class ETag {
// ETag is a validator based on https://tools.ietf.org/html/rfc7232#section-2.3.2
private:
Nullable<std::string> m_value;
public:
/**
* @brief The comparison type.
*/
enum class ETagComparison
{
Strong,
Weak
};
/*
2.3.2. Comparison
There are two entity-tag comparison functions, depending on whether
or not the comparison context allows the use of weak validators:
o Strong comparison: two entity-tags are equivalent if both are not
weak and their opaque-tags match character-by-character.
o Weak comparison: two entity-tags are equivalent if their
opaque-tags match character-by-character, regardless of either or
both being tagged as "weak".
+--------+--------+-------------------+-----------------+
| ETag 1 | ETag 2 | Strong Comparison | Weak Comparison |
+--------+--------+-------------------+-----------------+
| W/"1" | W/"1" | no match | match |
| W/"1" | W/"2" | no match | no match |
| W/"1" | "1" | no match | match |
| "1" | "1" | match | match |
+--------+--------+-------------------+-----------------+
// etag: //This is possible and means no etag is present
// etag:""
// etag:"*" //This means the etag is value '*'
// etag:"some value" //This means the etag is value 'some value'
// etag:/W"" //Weak eTag
// etag:* //This is special, means any etag
// If-Match header can do this
// If-Match:"value1","value2","value3" // Do this if any of these match
*/
/*
* @brief Indicates whether two #ETag values are equal.
* @param left #ETag to compare.
* @param right #ETag to compare.
* @param comparisonKind Determines what #ETagComparison to perform, default is
* #ETagComparison Strong.
* @return `true` if #ETag matches, `false` otherwise.
*/
static bool Equals(
const ETag& left,
const ETag& right,
const ETagComparison comparisonKind = ETagComparison::Strong)
{
// ETags are != if one of the values is null
if (!left.m_value || !right.m_value)
{
// Caveat, If both values are null then we consider the ETag equal
return !left.m_value && !right.m_value;
}
switch (comparisonKind)
{
case ETagComparison::Strong:
// Strong comparison
// If either is weak then there is no match
// else tags must match character for character
return !left.IsWeak() && !right.IsWeak()
&& (left.m_value.GetValue().compare(right.m_value.GetValue()) == 0);
break;
case ETagComparison::Weak:
auto leftStart = left.IsWeak() ? 2 : 0;
auto rightStart = right.IsWeak() ? 2 : 0;
auto leftVal = left.m_value.GetValue();
auto rightVal = right.m_value.GetValue();
// Compare if lengths are equal
// Compare the strings character by character
return ((leftVal.length() - leftStart) == (rightVal.length() - rightStart))
&& (leftVal.compare(leftStart, leftVal.length() - leftStart, &rightVal[rightStart])
== 0);
break;
}
// Unknown comparison
abort();
}
/**
* @brief Construct an empty (null) #ETag.
*/
ETag() = default;
/**
* @brief Construct a #ETag.
* @param etag The string value representation.
*/
explicit ETag(std::string etag) : m_value(std::move(etag)) {}
/**
* @brief Whether #ETag is present.
* @return `true` if #ETag has a value, `false` otherwise.
*/
bool HasValue() const { return m_value.HasValue(); }
/*
* @brief Returns the resource metadata represented as a string.
* @return #std::string
*/
const std::string& ToString() const
{
if (!m_value.HasValue())
{
abort();
}
return m_value.GetValue();
}
/**
* @brief Compare with \p other #ETag for equality.
* @param other Other #ETag to compare with.
* @return `true` if #ETag instances are equal according to strong validation, `false`
* otherwise.
*/
bool operator==(const ETag& other) const
{
return Equals(*this, other, ETagComparison::Strong);
}
/**
* @brief Compare with \p other #ETag for inequality.
* @param other Other #ETag to compare with.
* @return `true` if #ETag instances are not equal according to strong validation, `false`
* otherwise.
*/
bool operator!=(const ETag& other) const { return !(*this == other); }
/**
* @brief Specifies whether the #ETag is strong or weak.
* @return `true` if #ETag is a weak validator, `false` otherwise.
*/
bool IsWeak() const
{
// Null ETag is considered Strong
// Shortest valid weak etag has length of 4
// W/""
// Valid weak format must start with W/"
// Must end with a /"
const bool weak = m_value && (m_value.GetValue().length() >= 4)
&& ((m_value.GetValue()[0] == 'W') && (m_value.GetValue()[1] == '/')
&& (m_value.GetValue()[2] == '"')
&& (m_value.GetValue()[m_value.GetValue().size() - 1] == '"'));
return weak;
}
/**
* @brief #ETag representing everything.
* @notes The any #ETag is *, (unquoted). It is NOT the same as "*".
*/
static const ETag& Any()
{
static ETag any = ETag("*");
return any;
}
};
}} // namespace Azure::Core

View File

@ -35,6 +35,7 @@ add_executable (
${CURL_OPTIONS_TESTS}
${CURL_SESSION_TESTS}
datetime.cpp
etag.cpp
http.cpp
json.cpp
logging.cpp

View File

@ -0,0 +1,234 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include <gtest/gtest.h>
#include <azure/core/etag.hpp>
#include <limits>
using namespace Azure::Core;
TEST(ETag, ToString)
{
auto et1 = ETag("tag");
EXPECT_EQ(et1.ToString(), "tag");
auto et2 = ETag("\"tag\"");
EXPECT_EQ(et2.ToString(), "\"tag\"");
auto et3 = ETag("W/\"weakETag\"");
EXPECT_EQ(et3.ToString(), "W/\"weakETag\"");
auto strongETag
= ETag("\"#$%&'()*+,-./"
"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"");
EXPECT_EQ(
strongETag.ToString(),
"\"#$%&'()*+,-./"
"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"");
}
TEST(ETag, IsWeak)
{
auto et1 = ETag("tag");
EXPECT_FALSE(et1.IsWeak());
auto et2 = ETag("\"tag\"");
EXPECT_FALSE(et2.IsWeak());
auto et3 = ETag("W/\"weakETag\"");
EXPECT_TRUE(et3.IsWeak());
auto et4 = ETag("W/\"\"");
EXPECT_TRUE(et4.IsWeak());
auto any = ETag::Any();
EXPECT_FALSE(any.IsWeak());
}
TEST(ETag, Equals)
{
ETag empty;
ETag empty2;
auto weakTag = ETag("W/\"\"");
auto weakTag1 = ETag("W/\"1\"");
auto weakTag2 = ETag("W/\"Two\"");
auto strongTag1 = ETag("\"1\"");
auto strongTag2 = ETag("\"Two\"");
auto strongTagValidChars
= ETag("\"#$%&'()*+,-./"
"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"");
auto weakTagValidChars
= ETag("W/\"#$%&'()*+,-./"
"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"");
EXPECT_TRUE(empty == empty);
EXPECT_TRUE(empty2 == empty2);
EXPECT_TRUE(empty == empty2);
EXPECT_FALSE(weakTag == weakTag);
EXPECT_FALSE(weakTag1 == weakTag1);
EXPECT_FALSE(weakTag2 == weakTag2);
EXPECT_FALSE(weakTagValidChars == weakTagValidChars);
EXPECT_TRUE(strongTag1 == strongTag1);
EXPECT_TRUE(strongTag2 == strongTag2);
EXPECT_TRUE(strongTagValidChars == strongTagValidChars);
EXPECT_TRUE(weakTag != weakTag);
EXPECT_TRUE(weakTag1 != weakTag1);
EXPECT_TRUE(weakTag2 != weakTag2);
EXPECT_TRUE(weakTagValidChars != weakTagValidChars);
EXPECT_FALSE(strongTag1 != strongTag1);
EXPECT_FALSE(strongTag2 != strongTag2);
EXPECT_FALSE(strongTagValidChars != strongTagValidChars);
EXPECT_FALSE(weakTag == weakTag1);
EXPECT_FALSE(weakTag1 == weakTag);
EXPECT_FALSE(weakTagValidChars == strongTagValidChars);
EXPECT_TRUE(weakTag != weakTag1);
EXPECT_TRUE(weakTag1 != weakTag);
EXPECT_TRUE(weakTagValidChars != strongTagValidChars);
EXPECT_FALSE(weakTag1 == weakTag2);
EXPECT_FALSE(weakTag1 == strongTag1);
EXPECT_FALSE(strongTag1 == weakTag1);
EXPECT_TRUE(weakTag1 != weakTag2);
EXPECT_TRUE(weakTag1 != strongTag1);
EXPECT_TRUE(strongTag1 != weakTag1);
EXPECT_FALSE(weakTag2 == strongTag2);
EXPECT_FALSE(strongTag2 == weakTag2);
EXPECT_TRUE(weakTag2 != strongTag2);
EXPECT_TRUE(strongTag2 != weakTag2);
}
TEST(ETag, Any)
{
auto anyETag = ETag::Any();
auto star = ETag("*");
auto weakStar = ETag("W\"*\"");
auto quotedStar = ETag("\"*\"");
auto strongETag
= ETag("\"#$%&'()*+,-./"
"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\"");
EXPECT_TRUE(anyETag == anyETag);
EXPECT_TRUE(anyETag == ETag::Any());
EXPECT_FALSE(anyETag == strongETag);
EXPECT_TRUE(star == star);
EXPECT_TRUE(star == ETag::Any());
EXPECT_TRUE(star == anyETag);
EXPECT_FALSE(star == weakStar);
EXPECT_FALSE(weakStar == anyETag);
EXPECT_FALSE(quotedStar == weakStar);
EXPECT_FALSE(star == quotedStar);
EXPECT_TRUE(anyETag == star);
}
TEST(ETag, EqualsStrong)
{
// W/""
auto weakTag = ETag("W/\"\"");
// W/"1"
auto weakTag1 = ETag("W/\"1\"");
// W/"Two"
auto weakTagTwo = ETag("W/\"Two\"");
// W/"two"
auto weakTagtwo = ETag("W/\"two\"");
// "1"
auto strongTag1 = ETag("\"1\"");
// "Two"
auto strongTagTwo = ETag("\"Two\"");
// "two"
auto strongTagtwo = ETag("\"two\"");
EXPECT_FALSE(ETag::Equals(weakTag, weakTag, ETag::ETagComparison::Strong));
EXPECT_FALSE(ETag::Equals(weakTag1, weakTag1, ETag::ETagComparison::Strong));
EXPECT_FALSE(ETag::Equals(weakTagTwo, weakTagTwo, ETag::ETagComparison::Strong));
EXPECT_FALSE(ETag::Equals(weakTagtwo, weakTagtwo, ETag::ETagComparison::Strong));
EXPECT_TRUE(ETag::Equals(strongTag1, strongTag1, ETag::ETagComparison::Strong));
EXPECT_TRUE(ETag::Equals(strongTagTwo, strongTagTwo, ETag::ETagComparison::Strong));
EXPECT_TRUE(ETag::Equals(strongTagtwo, strongTagtwo, ETag::ETagComparison::Strong));
EXPECT_FALSE(ETag::Equals(weakTag, weakTag1, ETag::ETagComparison::Strong));
EXPECT_FALSE(ETag::Equals(weakTag1, weakTag, ETag::ETagComparison::Strong));
EXPECT_FALSE(ETag::Equals(weakTag1, weakTagTwo, ETag::ETagComparison::Strong));
EXPECT_FALSE(ETag::Equals(weakTagTwo, weakTag1, ETag::ETagComparison::Strong));
EXPECT_FALSE(ETag::Equals(weakTag1, strongTag1, ETag::ETagComparison::Strong));
EXPECT_FALSE(ETag::Equals(strongTag1, weakTag1, ETag::ETagComparison::Strong));
EXPECT_FALSE(ETag::Equals(weakTagTwo, strongTagTwo, ETag::ETagComparison::Strong));
EXPECT_FALSE(ETag::Equals(strongTagTwo, weakTagTwo, ETag::ETagComparison::Strong));
EXPECT_FALSE(ETag::Equals(strongTagTwo, weakTag1, ETag::ETagComparison::Strong));
EXPECT_FALSE(ETag::Equals(weakTag1, strongTagTwo, ETag::ETagComparison::Strong));
EXPECT_FALSE(ETag::Equals(strongTagTwo, strongTagtwo, ETag::ETagComparison::Strong));
EXPECT_FALSE(ETag::Equals(strongTagtwo, strongTagTwo, ETag::ETagComparison::Strong));
EXPECT_FALSE(ETag::Equals(weakTagTwo, weakTagtwo, ETag::ETagComparison::Strong));
EXPECT_FALSE(ETag::Equals(weakTagtwo, weakTagTwo, ETag::ETagComparison::Strong));
}
TEST(ETag, EqualsWeak)
{
// W/""
auto weakTag = ETag("W/\"\"");
// W/"1"
auto weakTag1 = ETag("W/\"1\"");
// W/"Two"
auto weakTagTwo = ETag("W/\"Two\"");
// W/"two"
auto weakTagtwo = ETag("W/\"two\"");
// "1"
auto strongTag1 = ETag("\"1\"");
// "Two"
auto strongTagTwo = ETag("\"Two\"");
// "two"
auto strongTagtwo = ETag("\"two\"");
EXPECT_TRUE(ETag::Equals(weakTag, weakTag, ETag::ETagComparison::Weak));
EXPECT_TRUE(ETag::Equals(weakTag1, weakTag1, ETag::ETagComparison::Weak));
EXPECT_TRUE(ETag::Equals(weakTagTwo, weakTagTwo, ETag::ETagComparison::Weak));
EXPECT_TRUE(ETag::Equals(weakTagtwo, weakTagtwo, ETag::ETagComparison::Weak));
EXPECT_TRUE(ETag::Equals(strongTag1, strongTag1, ETag::ETagComparison::Weak));
EXPECT_TRUE(ETag::Equals(strongTagTwo, strongTagTwo, ETag::ETagComparison::Weak));
EXPECT_FALSE(ETag::Equals(weakTag, weakTag1, ETag::ETagComparison::Weak));
EXPECT_FALSE(ETag::Equals(weakTag1, weakTag, ETag::ETagComparison::Weak));
EXPECT_FALSE(ETag::Equals(weakTag1, weakTagTwo, ETag::ETagComparison::Weak));
EXPECT_FALSE(ETag::Equals(weakTagTwo, weakTag1, ETag::ETagComparison::Weak));
EXPECT_TRUE(ETag::Equals(weakTag1, strongTag1, ETag::ETagComparison::Weak));
EXPECT_TRUE(ETag::Equals(strongTag1, weakTag1, ETag::ETagComparison::Weak));
EXPECT_TRUE(ETag::Equals(weakTagTwo, strongTagTwo, ETag::ETagComparison::Weak));
EXPECT_TRUE(ETag::Equals(strongTagTwo, weakTagTwo, ETag::ETagComparison::Weak));
EXPECT_FALSE(ETag::Equals(strongTagTwo, weakTag1, ETag::ETagComparison::Weak));
EXPECT_FALSE(ETag::Equals(weakTag1, strongTagTwo, ETag::ETagComparison::Weak));
EXPECT_FALSE(ETag::Equals(strongTagTwo, weakTagtwo, ETag::ETagComparison::Weak));
EXPECT_FALSE(ETag::Equals(weakTagtwo, strongTagTwo, ETag::ETagComparison::Weak));
EXPECT_FALSE(ETag::Equals(strongTagTwo, strongTagtwo, ETag::ETagComparison::Weak));
EXPECT_FALSE(ETag::Equals(strongTagtwo, strongTagTwo, ETag::ETagComparison::Weak));
EXPECT_FALSE(ETag::Equals(weakTagTwo, weakTagtwo, ETag::ETagComparison::Weak));
EXPECT_FALSE(ETag::Equals(weakTagtwo, weakTagTwo, ETag::ETagComparison::Weak));
}

View File

@ -17,13 +17,14 @@ class DllExportTest {
AZ_CORE_DLLEXPORT static const bool DllExportHIncluded;
};
TEST(Logging, simplifiedHeader)
TEST(SimplifiedHeader, core)
{
EXPECT_NO_THROW(Azure::Core::Context c);
EXPECT_NO_THROW(Azure::Core::DateTime(2020, 11, 03, 15, 30, 44));
EXPECT_NO_THROW(Azure::Core::Nullable<int> n);
EXPECT_NO_THROW(Azure::Core::ETag e);
EXPECT_NO_THROW(Azure::Core::Http::RawResponse r(
1, 1, Azure::Core::Http::HttpStatusCode::Accepted, "phrase"));
EXPECT_NO_THROW(Azure::Core::Nullable<int> n);
EXPECT_NO_THROW(Azure::Core::Uuid::CreateUuid());
EXPECT_NO_THROW(Azure::Core::Details::Version::VersionString());