parent
60a18e3c3f
commit
bcb684f95e
@ -6,6 +6,7 @@
|
||||
|
||||
- Added a WinHTTP-based `HttpTransport` called `WinHttpTransport` and use that as the default `TransportPolicyOptions.Transport` on Windows when sending and receiving requests and responses over the wire.
|
||||
- Added `Range` type to `Azure::Core::Http` namespace.
|
||||
- Added support for long-running operations with `Operation<T>`.
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
|
||||
@ -67,6 +67,8 @@ set(
|
||||
inc/azure/core/datetime.hpp
|
||||
inc/azure/core/exception.hpp
|
||||
inc/azure/core/nullable.hpp
|
||||
inc/azure/core/operation.hpp
|
||||
inc/azure/core/operation_status.hpp
|
||||
inc/azure/core/platform.hpp
|
||||
inc/azure/core/response.hpp
|
||||
inc/azure/core/strings.hpp
|
||||
@ -93,6 +95,7 @@ set(
|
||||
src/logging/logging.cpp
|
||||
src/context.cpp
|
||||
src/datetime.cpp
|
||||
src/operation_status.cpp
|
||||
src/strings.cpp
|
||||
src/version.cpp
|
||||
)
|
||||
|
||||
125
sdk/core/azure-core/inc/azure/core/operation.hpp
Normal file
125
sdk/core/azure-core/inc/azure/core/operation.hpp
Normal file
@ -0,0 +1,125 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Provides support for long-running operations.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "azure/core/context.hpp"
|
||||
#include "azure/core/operation_status.hpp"
|
||||
#include "azure/core/response.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace Azure { namespace Core {
|
||||
|
||||
/**
|
||||
* @brief Methods starting long-running operations return Operation<T> types.
|
||||
*
|
||||
* @tparam T The long-running operation final result type.
|
||||
*/
|
||||
template <class T> class Operation {
|
||||
private:
|
||||
// These are pure virtual b/c the derived class must provide an implementation
|
||||
virtual std::unique_ptr<Http::RawResponse> PollInternal(Context& context) = 0;
|
||||
virtual Response<T> PollUntilDoneInternal(Context& context, std::chrono::milliseconds period)
|
||||
= 0;
|
||||
|
||||
protected:
|
||||
OperationStatus m_status = OperationStatus::NotStarted;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Final reuslt of the long-running operation.
|
||||
*
|
||||
* @return Response<T> the final result of the long-running operation.
|
||||
*/
|
||||
virtual T Value() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Gets an token representing the operation that can be used to poll for the status of
|
||||
* the long-running operation.
|
||||
*
|
||||
* @return std::string The resume token.
|
||||
*/
|
||||
virtual std::string GetResumeToken() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Returns the current #OperationStatus of the long-running operation.
|
||||
*/
|
||||
OperationStatus Status() const noexcept { return m_status; }
|
||||
|
||||
/**
|
||||
* @brief Returns true if the long-running operation completed.
|
||||
*
|
||||
* @return `true` if the long-running operation is done. `false` otherwise.
|
||||
*/
|
||||
bool IsDone() const noexcept
|
||||
{
|
||||
return (
|
||||
m_status == OperationStatus::Succeeded || m_status == OperationStatus::Cancelled
|
||||
|| m_status == OperationStatus::Failed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns true if the long-running operation completed successfully and has produced a
|
||||
* final result. The final result is accessible from Value().
|
||||
*
|
||||
* @return `true` if the long-running operation completed successfully. `false` otherwise.
|
||||
*/
|
||||
bool HasValue() const noexcept { return (m_status == OperationStatus::Succeeded); }
|
||||
|
||||
/**
|
||||
* @brief Calls the server to get updated status of the long-running operation.
|
||||
*
|
||||
* @return An HTTP #RawResponse returned from the service.
|
||||
*/
|
||||
std::unique_ptr<Http::RawResponse> Poll()
|
||||
{
|
||||
// In the cases where the customer doesn't want to use a context we new one up and pass it
|
||||
// through
|
||||
return PollInternal(GetApplicationContext());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Calls the server to get updated status of the long-running operation.
|
||||
*
|
||||
* @param context #Context allows the user to cancel the long-running operation.
|
||||
*
|
||||
* @return An HTTP #RawResponse returned from the service.
|
||||
*/
|
||||
std::unique_ptr<Http::RawResponse> Poll(Context& context) { return PollInternal(context); }
|
||||
|
||||
/**
|
||||
* @brief Periodically calls the server till the long-running operation completes;
|
||||
*
|
||||
* @param period Time in milliseconds to wait between polls
|
||||
*
|
||||
* @return Response<T> the final result of the long-running operation.
|
||||
*/
|
||||
Response<T> PollUntilDone(std::chrono::milliseconds period)
|
||||
{
|
||||
// In the cases where the customer doesn't want to use a context we new one up and pass it
|
||||
// through
|
||||
return PollUntilDoneInternal(GetApplicationContext(), period);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Periodically calls the server till the long-running operation completes;
|
||||
*
|
||||
* @param context #Context allows the user to cancel the long-running operation.
|
||||
* @param period Time in milliseconds to wait between polls
|
||||
*
|
||||
* @return Response<T> the final result of the long-running operation.
|
||||
*/
|
||||
Response<T> PollUntilDone(Context& context, std::chrono::milliseconds period)
|
||||
{
|
||||
return PollUntilDoneInternal(context, period);
|
||||
}
|
||||
};
|
||||
}} // namespace Azure::Core
|
||||
78
sdk/core/azure-core/inc/azure/core/operation_status.hpp
Normal file
78
sdk/core/azure-core/inc/azure/core/operation_status.hpp
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Valid states for long-running Operations. Services can extend upon the default set of
|
||||
* values.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <utility> // for std::move
|
||||
|
||||
#include "azure/core/strings.hpp"
|
||||
|
||||
namespace Azure { namespace Core {
|
||||
|
||||
/**
|
||||
* @brief Long-running operation states.
|
||||
*/
|
||||
class OperationStatus {
|
||||
std::string m_value;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a #OperationStatus with \p value.
|
||||
*
|
||||
* @param value A non-absent value to initialize with.
|
||||
*/
|
||||
explicit OperationStatus(const std::string& value) : m_value(value) {}
|
||||
/**
|
||||
* @brief Construct a #OperationStatus with \p value.
|
||||
*
|
||||
* @param value A non-absent value to initialize with.
|
||||
*/
|
||||
explicit OperationStatus(std::string&& value) : m_value(std::move(value)) {}
|
||||
/**
|
||||
* @brief Construct a #OperationStatus with \p value.
|
||||
*
|
||||
* @param value A non-absent value to initialize with.
|
||||
*/
|
||||
explicit OperationStatus(const char* value) : m_value(value) {}
|
||||
|
||||
/**
|
||||
* @brief Compare two #OperationStatus objects
|
||||
*
|
||||
* @param other A #OperationStatus to compare with.
|
||||
*
|
||||
* @return `true` if the states have the same string representation. `false` otherwise.
|
||||
*/
|
||||
bool operator==(const OperationStatus& other) const noexcept
|
||||
{
|
||||
return Strings::LocaleInvariantCaseInsensitiveEqual(m_value, other.m_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compare two #OperationStatus objects
|
||||
*
|
||||
* @param other A #OperationStatus to compare with.
|
||||
*
|
||||
* @return `false` if the states have the same string representation. `true` otherwise.
|
||||
*/
|
||||
bool operator!=(const OperationStatus& other) const noexcept { return !(*this == other); }
|
||||
|
||||
/**
|
||||
* @brief The std::string representation of the value
|
||||
*/
|
||||
const std::string& Get() const noexcept { return m_value; }
|
||||
|
||||
static const OperationStatus NotStarted;
|
||||
static const OperationStatus Running;
|
||||
static const OperationStatus Succeeded;
|
||||
static const OperationStatus Cancelled;
|
||||
static const OperationStatus Failed;
|
||||
};
|
||||
|
||||
}} // namespace Azure::Core
|
||||
@ -97,7 +97,7 @@ namespace Azure { namespace Core {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get a smaprt pointer rvalue reference to the value of a specific type.
|
||||
* @brief Get a smart pointer rvalue reference to the value of a specific type.
|
||||
*/
|
||||
std::unique_ptr<Http::RawResponse>&& ExtractRawResponse()
|
||||
{
|
||||
|
||||
14
sdk/core/azure-core/src/operation_status.cpp
Normal file
14
sdk/core/azure-core/src/operation_status.cpp
Normal file
@ -0,0 +1,14 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include "azure/core/operation_status.hpp"
|
||||
|
||||
namespace Azure { namespace Core {
|
||||
|
||||
const OperationStatus OperationStatus::NotStarted("NotStarted");
|
||||
const OperationStatus OperationStatus::Running{"Running"};
|
||||
const OperationStatus OperationStatus::Succeeded{"Succeeded"};
|
||||
const OperationStatus OperationStatus::Failed{"Failed"};
|
||||
const OperationStatus OperationStatus::Cancelled{"Cancelled"};
|
||||
|
||||
}} // namespace Azure::Core
|
||||
@ -42,8 +42,10 @@ add_executable (
|
||||
logging.cpp
|
||||
main.cpp
|
||||
nullable.cpp
|
||||
operation.cpp
|
||||
operation_status.cpp
|
||||
pipeline.cpp
|
||||
policy.cpp
|
||||
policy.cpp
|
||||
simplified_header.cpp
|
||||
string.cpp
|
||||
telemetry_policy.cpp
|
||||
|
||||
90
sdk/core/azure-core/test/ut/operation.cpp
Normal file
90
sdk/core/azure-core/test/ut/operation.cpp
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "operation_test.hpp"
|
||||
|
||||
#include <azure/core/context.hpp>
|
||||
#include <azure/core/operation.hpp>
|
||||
#include <azure/core/operation_status.hpp>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
using namespace Azure::Core;
|
||||
using namespace Azure::Core::Test;
|
||||
using namespace std::literals;
|
||||
|
||||
TEST(Operation, Poll)
|
||||
{
|
||||
StringClient client;
|
||||
auto operation = client.StartStringUpdate();
|
||||
|
||||
EXPECT_FALSE(operation.IsDone());
|
||||
EXPECT_FALSE(operation.HasValue());
|
||||
|
||||
while(!operation.IsDone())
|
||||
{
|
||||
EXPECT_FALSE(operation.HasValue());
|
||||
EXPECT_THROW(operation.Value(), std::runtime_error);
|
||||
auto response = operation.Poll();
|
||||
}
|
||||
|
||||
EXPECT_TRUE(operation.IsDone());
|
||||
EXPECT_TRUE(operation.HasValue());
|
||||
|
||||
auto result = operation.Value();
|
||||
EXPECT_TRUE(result == "StringOperation-Completed");
|
||||
}
|
||||
|
||||
TEST(Operation, PollUntilDone)
|
||||
{
|
||||
StringClient client;
|
||||
auto operation = client.StartStringUpdate();
|
||||
|
||||
EXPECT_FALSE(operation.IsDone());
|
||||
EXPECT_FALSE(operation.HasValue());
|
||||
EXPECT_THROW(operation.Value(), std::runtime_error);
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
auto response = operation.PollUntilDone(500ms);
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
std::chrono::duration<double, std::milli> elapsed = end - start;
|
||||
//StringOperation test code is implemented to poll 2 times
|
||||
EXPECT_TRUE(elapsed >= 1s);
|
||||
|
||||
EXPECT_TRUE(operation.IsDone());
|
||||
EXPECT_TRUE(operation.HasValue());
|
||||
|
||||
auto result = operation.Value();
|
||||
EXPECT_EQ(result, "StringOperation-Completed");
|
||||
}
|
||||
|
||||
TEST(Operation, Status)
|
||||
{
|
||||
StringClient client;
|
||||
auto operation = client.StartStringUpdate();
|
||||
|
||||
EXPECT_FALSE(operation.IsDone());
|
||||
EXPECT_FALSE(operation.HasValue());
|
||||
EXPECT_THROW(operation.Value(), std::runtime_error);
|
||||
EXPECT_EQ(operation.Status(), OperationStatus::NotStarted);
|
||||
|
||||
operation.SetOperationStatus(OperationStatus::Running);
|
||||
EXPECT_FALSE(operation.IsDone());
|
||||
EXPECT_FALSE(operation.HasValue());
|
||||
EXPECT_THROW(operation.Value(), std::runtime_error);
|
||||
EXPECT_EQ(operation.Status(), OperationStatus::Running);
|
||||
|
||||
operation.SetOperationStatus(OperationStatus::Failed);
|
||||
EXPECT_TRUE(operation.IsDone());
|
||||
EXPECT_FALSE(operation.HasValue());
|
||||
EXPECT_THROW(operation.Value(), std::runtime_error);
|
||||
EXPECT_EQ(operation.Status(), OperationStatus::Failed);
|
||||
|
||||
operation.SetOperationStatus(OperationStatus::Cancelled);
|
||||
EXPECT_TRUE(operation.IsDone());
|
||||
EXPECT_FALSE(operation.HasValue());
|
||||
EXPECT_THROW(operation.Value(), std::runtime_error);
|
||||
EXPECT_EQ(operation.Status(), OperationStatus::Cancelled);
|
||||
}
|
||||
51
sdk/core/azure-core/test/ut/operation_status.cpp
Normal file
51
sdk/core/azure-core/test/ut/operation_status.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <azure/core/operation_status.hpp>
|
||||
|
||||
using namespace Azure::Core;
|
||||
|
||||
TEST(OperationStatus, Basic)
|
||||
{
|
||||
OperationStatus status = OperationStatus::Cancelled;
|
||||
EXPECT_EQ(status, OperationStatus::Cancelled);
|
||||
EXPECT_EQ(status.Get(), "Cancelled");
|
||||
|
||||
status = OperationStatus::Failed;
|
||||
EXPECT_EQ(status, OperationStatus::Failed);
|
||||
EXPECT_EQ(status.Get(), "Failed");
|
||||
|
||||
status = OperationStatus::NotStarted;
|
||||
EXPECT_EQ(status, OperationStatus::NotStarted);
|
||||
EXPECT_EQ(status.Get(), "NotStarted");
|
||||
|
||||
status = OperationStatus::Running;
|
||||
EXPECT_EQ(status, OperationStatus::Running);
|
||||
EXPECT_EQ(status.Get(), "Running");
|
||||
|
||||
status = OperationStatus::Succeeded;
|
||||
EXPECT_EQ(status, OperationStatus::Succeeded);
|
||||
EXPECT_EQ(status.Get(), "Succeeded");
|
||||
}
|
||||
|
||||
TEST(OperationStatus, Custom)
|
||||
{
|
||||
OperationStatus status1("CustomValue");
|
||||
EXPECT_EQ(status1.Get(), "CustomValue");
|
||||
EXPECT_NE(status1, OperationStatus::NotStarted);
|
||||
|
||||
OperationStatus status2 = OperationStatus("CustomValue");
|
||||
EXPECT_EQ(status2.Get(), "CustomValue");
|
||||
EXPECT_NE(status2, OperationStatus::NotStarted);
|
||||
|
||||
std::string custom("CustomValue");
|
||||
OperationStatus status3 = OperationStatus(custom);
|
||||
EXPECT_EQ(status3.Get(), custom);
|
||||
EXPECT_NE(status3, OperationStatus::NotStarted);
|
||||
|
||||
OperationStatus status4 = OperationStatus(std::string("CustomValue"));
|
||||
EXPECT_EQ(status4.Get(), "CustomValue");
|
||||
EXPECT_NE(status4, OperationStatus::NotStarted);
|
||||
}
|
||||
91
sdk/core/azure-core/test/ut/operation_test.hpp
Normal file
91
sdk/core/azure-core/test/ut/operation_test.hpp
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <azure/core/context.hpp>
|
||||
#include <azure/core/nullable.hpp>
|
||||
#include <azure/core/operation.hpp>
|
||||
#include <azure/core/operation_status.hpp>
|
||||
#include <azure/core/response.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace Azure { namespace Core { namespace Test {
|
||||
|
||||
class StringClient;
|
||||
|
||||
class StringOperation : public Operation<std::string> {
|
||||
|
||||
private:
|
||||
StringClient* m_client;
|
||||
std::string m_operationToken;
|
||||
std::string m_value;
|
||||
|
||||
private:
|
||||
int m_count = 0;
|
||||
|
||||
private:
|
||||
std::unique_ptr<Http::RawResponse> PollInternal(Context& context) override
|
||||
{
|
||||
// Artificial delay to require 2 polls
|
||||
if (++m_count == 2)
|
||||
{
|
||||
m_status = OperationStatus::Succeeded;
|
||||
m_value = "StringOperation-Completed";
|
||||
}
|
||||
|
||||
// The contents of the response are irrelevant for testing purposes
|
||||
// Need only ensure that a RawResponse is returned
|
||||
return std::make_unique<Http::RawResponse>(
|
||||
(uint16_t)1, (uint16_t)0, Http::HttpStatusCode(200), "OK");
|
||||
}
|
||||
|
||||
Response<std::string> PollUntilDoneInternal(Context& context, std::chrono::milliseconds period)
|
||||
override
|
||||
{
|
||||
std::unique_ptr<Http::RawResponse> response;
|
||||
while (!IsDone())
|
||||
{
|
||||
// Sleep for the period
|
||||
// Actual clients should respect the retry after header if present
|
||||
std::this_thread::sleep_for(period);
|
||||
|
||||
response = Poll(context);
|
||||
}
|
||||
|
||||
return Response<std::string>(m_value, std::move(response));
|
||||
}
|
||||
|
||||
public:
|
||||
StringOperation(StringClient* client) : m_client(client) {}
|
||||
|
||||
std::string GetResumeToken() const override { return m_operationToken; }
|
||||
|
||||
std::string Value() const override
|
||||
{
|
||||
if (m_status != OperationStatus::Succeeded)
|
||||
{
|
||||
throw std::runtime_error("InvalidOperation");
|
||||
}
|
||||
|
||||
return m_value;
|
||||
}
|
||||
|
||||
// This is a helper method to allow testing of the underlying operation<T> behaviors
|
||||
// ClientOperations would not expose a way to control status
|
||||
void SetOperationStatus(OperationStatus status) { m_status = status; }
|
||||
};
|
||||
|
||||
class StringClient {
|
||||
public:
|
||||
StringOperation StartStringUpdate()
|
||||
{
|
||||
// Make initial String call
|
||||
StringOperation operation = StringOperation(this);
|
||||
return operation;
|
||||
}
|
||||
};
|
||||
|
||||
}}} // namespace Azure::Core::Test
|
||||
Loading…
Reference in New Issue
Block a user