diff --git a/sdk/core/azure-core/inc/azure/core/azure.hpp b/sdk/core/azure-core/inc/azure/core/azure.hpp index 66c0f599a..04b12a1e4 100644 --- a/sdk/core/azure-core/inc/azure/core/azure.hpp +++ b/sdk/core/azure-core/inc/azure/core/azure.hpp @@ -1,11 +1,19 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +/** + * @file + * @brief Common definitions. + */ + #pragma once #include #include +/** + * @brief Used in implementations to mark an unreferenced function parameter. + */ #define AZURE_UNREFERENCED_PARAMETER(x) ((void)(x)); namespace Azure { namespace Core { namespace Details { diff --git a/sdk/core/azure-core/inc/azure/core/context.hpp b/sdk/core/azure-core/inc/azure/core/context.hpp index 1b3fa13ea..9158e3023 100644 --- a/sdk/core/azure-core/inc/azure/core/context.hpp +++ b/sdk/core/azure-core/inc/azure/core/context.hpp @@ -1,6 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +/** + * @file + * @brief Context for canceling long running operations. + */ + #pragma once #include @@ -12,23 +17,30 @@ namespace Azure { namespace Core { + /** + * @ A base abstract class for the `std::unique_ptr` value representation in #ContextValue. + */ struct ValueBase { virtual ~ValueBase() {} }; - /* - * @brief ContextValue exists as a substitute for variant which isn't available until C++17 + /** + * @brief Represents a value that is associated with context. + * @rmark Exists as a substitute for variant which isn't available until C++17. */ class ContextValue { public: + /** + * @brief A type of context value. + */ enum class ContextValueType { - Undefined, - Bool, - Int, - StdString, - UniquePtr + Undefined, ///< Undefined. + Bool, ///< `bool`. + Int, ///< `int`. + StdString, ///< `std::string` + UniquePtr ///< `std::unique_ptr` }; private: @@ -47,26 +59,64 @@ namespace Azure { namespace Core { #pragma warning(disable : 26495) #endif + /** + * @brief Create a context value with undefined value type. + */ ContextValue() noexcept : m_contextValueType(ContextValueType::Undefined) {} + + /** + * @brief Create a context value with `bool` value type. + * + * @param b Boolean value. + */ ContextValue(bool b) noexcept : m_contextValueType(ContextValueType::Bool), m_b(b) {} + + /** + * @brief Create a context value with `int` value type. + * + * @param i Integer value. + */ ContextValue(int i) noexcept : m_contextValueType(ContextValueType::Int), m_i(i) {} + + /** + * @brief Create a context value with `std::string` value type. + * + * @param s String value. + */ ContextValue(const std::string& s) : m_contextValueType(ContextValueType::StdString), m_s(s) {} - ContextValue(const char* s) - : m_contextValueType(ContextValueType::StdString), m_s(s) - { - } + /** + * @brief Create a context value with `std::string` value type. + * + * @param s String value represented as 0-terminated C-string. + */ + ContextValue(const char* s) : m_contextValueType(ContextValueType::StdString), m_s(s) {} + /** + * @brief Create a context value with `std::string` value type, using `std::move` semantics. + * + * @param s String value. + */ ContextValue(std::string&& s) : m_contextValueType(ContextValueType::StdString), m_s(std::move(s)) { } + /** + * @brief Create a context value with `std::unique_ptr` value type. + * + * @param p Smart pointer to #ValueBase. + */ ContextValue(std::unique_ptr&& p) noexcept : m_contextValueType(ContextValueType::UniquePtr), m_p(std::move(p)) { } + /** + * @brief Move constructor. + * + * @param other An rvalue reference to another instance of #ContextValue. + */ ContextValue(ContextValue&& other) noexcept : m_contextValueType(other.m_contextValueType) { switch (m_contextValueType) @@ -91,6 +141,9 @@ namespace Azure { namespace Core { #pragma warning(pop) #endif + /** + * @brief #ContextValue destructor. + */ ~ContextValue() { switch (m_contextValueType) @@ -110,11 +163,22 @@ namespace Azure { namespace Core { ContextValue& operator=(const ContextValue& other) = delete; + /** + * @brief Get the context value. + * + * @tparam Expected The type of value to get. + */ template const ExpectedType& Get() const noexcept; + /** + * @brief Get the context value type. + */ ContextValueType Alternative() const noexcept { return m_contextValueType; } }; + /** + * @brief Get the context value type as boolean (`bool`). + */ template <> inline const bool& ContextValue::Get() const noexcept { if (m_contextValueType != ContextValueType::Bool) @@ -124,6 +188,9 @@ namespace Azure { namespace Core { return m_b; } + /** + * @brief Get the context value type as integer (`int`). + */ template <> inline const int& ContextValue::Get() const noexcept { if (m_contextValueType != ContextValueType::Int) @@ -133,6 +200,9 @@ namespace Azure { namespace Core { return m_i; } + /** + * @brief Get the context value type as string (`std::string`). + */ template <> inline const std::string& ContextValue::Get() const noexcept { if (m_contextValueType != ContextValueType::StdString) @@ -142,6 +212,9 @@ namespace Azure { namespace Core { return m_s; } + /** + * @brief Get the context value type as a pointer (`std::unique_ptr`). + */ template <> inline const std::unique_ptr& ContextValue::Get() const noexcept { if (m_contextValueType != ContextValueType::UniquePtr) @@ -151,8 +224,14 @@ namespace Azure { namespace Core { return m_p; } + /** + * @brief A context is a node within a tree that represents expiration times and key/value pairs. + */ class Context { public: + /** + * @brief A type used to provide a point in time for context expiration. + */ using time_point = std::chrono::system_clock::time_point; private: @@ -195,24 +274,58 @@ namespace Azure { namespace Core { } public: + /** + * @brief Construct a new context with no expiration, and no value associated. + */ Context() : m_contextSharedState(std::make_shared()) {} + /** + * @brief Copy constructor. + */ Context& operator=(const Context&) = default; + /** + * @brief Create a context with expiration. + * + * @param cancelWhen A point in time after which a context expires. + * + * @return A child context with expiration. + */ Context WithDeadline(time_point cancelWhen) const { return Context{std::make_shared( m_contextSharedState, cancelWhen, std::string(), ContextValue{})}; } + /** + * @brief Create a context without an expiration, but with \p key and \p value associated with + * it. + * + * @param key A key to associate with this context. + * @param value A value to associate with this context. + * + * @return A child context with no expiration and the \p key and \p value associated with it. + */ Context WithValue(const std::string& key, ContextValue&& value) const { return Context{std::make_shared( m_contextSharedState, time_point::max(), key, std::move(value))}; } + /** + * @ + */ time_point CancelWhen() const; + /** + * @brief Get a value associated with a \p key parameter within this context or the branch of + * contexts this context belongs to. + * + * @param key A key assiciated with a context to find. + * + * @return A value associated with the context found; an empty value if a specific value can't + * be found. + */ const ContextValue& operator[](const std::string& key) const { if (!key.empty()) @@ -230,6 +343,15 @@ namespace Azure { namespace Core { return empty; } + /** + * @brief Check whether the context has a key matching \p key parameter in it itself, or in the + * branch the context belongs to. + * + * @param key A key assiciated with a context to find. + * + * @return `true` if this context, or the tree branch this context belongs to has a \p key + * associsted with it. `false` otherwise. + */ bool HasKey(const std::string& key) const { if (!key.empty()) @@ -245,12 +367,18 @@ namespace Azure { namespace Core { return false; } + /** + * @brief Cancels the context. + */ void Cancel() { m_contextSharedState->CancelAtMsecSinceEpoch = ContextSharedState::ToMsecSinceEpoch(time_point::min()); } + /** + * @brief Throw an exception if the context was canceled. + */ void ThrowIfCanceled() const { if (CancelWhen() < std::chrono::system_clock::now()) @@ -261,5 +389,8 @@ namespace Azure { namespace Core { } }; + /** + * @brief Get the application context (root). + */ Context& GetApplicationContext(); }} // namespace Azure::Core diff --git a/sdk/core/azure-core/inc/azure/core/credentials/credentials.hpp b/sdk/core/azure-core/inc/azure/core/credentials/credentials.hpp index 7db28ad15..833b56086 100644 --- a/sdk/core/azure-core/inc/azure/core/credentials/credentials.hpp +++ b/sdk/core/azure-core/inc/azure/core/credentials/credentials.hpp @@ -1,6 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +/** + * @file + * @brief Credentials used for authentication with many (not all) Azure SDK client libraries. + */ + #pragma once #include "azure/core/context.hpp" @@ -14,17 +19,37 @@ namespace Azure { namespace Core { namespace Credentials { + /** + * @brief Represents an access token. + */ struct AccessToken { + /** + * @brief Token string. + */ std::string Token; + + /** + * @brief Token expiration. + */ std::chrono::system_clock::time_point ExpiresOn; }; + /** + * @brief Token credential. + */ class TokenCredential { public: + /** + * @brief Get an authentication token. + * + * @param context #Context so that operation can be canceled. + * @param scopes Authentication scopes. + */ virtual AccessToken GetToken(Context const& context, std::vector const& scopes) const = 0; + /// Destructor. virtual ~TokenCredential() = default; protected: @@ -35,6 +60,10 @@ namespace Azure { namespace Core { namespace Credentials { void operator=(TokenCredential const&) = delete; }; + /** + * @brief This class is used by Azure SDK clients to authenticate with the Azure service using a + * tenant ID, client ID and client secret. + */ class ClientSecretCredential : public TokenCredential { private: static std::string const g_aadGlobalAuthority; @@ -45,6 +74,19 @@ namespace Azure { namespace Core { namespace Credentials { std::string m_authority; public: + /** + * @brief Construct a Client Secret credential. + * + * @param tenantId Tenant ID. + * @param clientId Client ID. + * @param clientSecret Client Secret. + * @param authority Authentication authority URL to set. If omitted, initializes credential with + * default authority (Azure AD global authority - "https://login.microsoftonline.com/"). + * + * @note Example of a \p authority string: "https://login.microsoftonline.us/". See national + * clouds' Azure AD authentication endpoints: + * https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-national-cloud. + */ explicit ClientSecretCredential( std::string tenantId, std::string clientId, @@ -59,15 +101,24 @@ namespace Azure { namespace Core { namespace Credentials { const override; }; + /** + * @brief An exception that gets thrown when authentication error occurs. + */ class AuthenticationException : public std::runtime_error { public: explicit AuthenticationException(std::string const& msg) : std::runtime_error(msg) {} }; + /** + * @brief An environment credential. + */ class EnvironmentCredential : public TokenCredential { std::unique_ptr m_credentialImpl; public: + /** + * Constructs an environment credential. + */ explicit EnvironmentCredential(); AccessToken GetToken(Context const& context, std::vector const& scopes) diff --git a/sdk/core/azure-core/inc/azure/core/credentials/policy/policies.hpp b/sdk/core/azure-core/inc/azure/core/credentials/policy/policies.hpp index 14ee4db9a..81e948960 100644 --- a/sdk/core/azure-core/inc/azure/core/credentials/policy/policies.hpp +++ b/sdk/core/azure-core/inc/azure/core/credentials/policy/policies.hpp @@ -1,6 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +/** + * @file + * @brief Authentication policies. + */ + #pragma once #include @@ -12,6 +17,9 @@ namespace Azure { namespace Core { namespace Credentials { namespace Policy { + /** + * @brief Bearer Token authentication policy. + */ class BearerTokenAuthenticationPolicy : public Http::HttpPolicy { private: std::shared_ptr const m_credential; @@ -24,6 +32,12 @@ namespace Azure { namespace Core { namespace Credentials { namespace Policy { void operator=(BearerTokenAuthenticationPolicy const&) = delete; public: + /** + * @brief Construct a Bearer Token authentication policy with single authentication scope. + * + * @param credential A #TokenCredential to use with this policy. + * @param scope Authentication scope. + */ explicit BearerTokenAuthenticationPolicy( std::shared_ptr credential, std::string scope) @@ -32,6 +46,12 @@ namespace Azure { namespace Core { namespace Credentials { namespace Policy { m_scopes.emplace_back(std::move(scope)); } + /** + * @brief Construct a Bearer Token authentication policy with multiple authentication scopes. + * + * @param credential A #TokenCredential to use with this policy. + * @param scopes A vector of authentication scopes. + */ explicit BearerTokenAuthenticationPolicy( std::shared_ptr credential, std::vector scopes) @@ -39,6 +59,16 @@ namespace Azure { namespace Core { namespace Credentials { namespace Policy { { } + /** + * @brief Construct a Bearer Token authentication policy with multiple authentication scopes. + * + * @tparam A type of scopes sequence iterator. + * + * @param credential A #TokenCredential to use with this policy. + * @param scopesBegin An iterator pointing to begin of the sequence of scopes to use. + * @param scopesEnd An iterator pointing to an element after the last element in sequence of + * scopes to use. + */ template explicit BearerTokenAuthenticationPolicy( std::shared_ptr credential, diff --git a/sdk/core/azure-core/inc/azure/core/http/body_stream.hpp b/sdk/core/azure-core/inc/azure/core/http/body_stream.hpp index 9b1a79f76..38c0e0759 100644 --- a/sdk/core/azure-core/inc/azure/core/http/body_stream.hpp +++ b/sdk/core/azure-core/inc/azure/core/http/body_stream.hpp @@ -1,6 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +/** + * @file + * @brief BodyStream is used to read data to/from a service. + */ + #pragma once #ifdef POSIX @@ -26,36 +31,74 @@ namespace Azure { namespace Core { namespace Http { - // BodyStream is used to read data to/from a service + /** + *@brief Used to read data to/from a service. + */ class BodyStream { public: + /// Destructor. virtual ~BodyStream() = default; - // Returns the length of the data; used with the HTTP Content-Length header + /** + * @brief Get the length of the data. + * @remark Used with the HTTP `Content-Length` header. + */ virtual int64_t Length() const = 0; - // Resets the stream back to the beginning (for retries) - // Derived classes that send data in an HTTP request MUST override this and implement it - // properly. + /* + * @brief Resets the stream back to the beginning (for retries). + * @remark Derived classes that send data in an HTTP request MUST override this and implement it + * properly. + */ virtual void Rewind() { throw "Not Implemented"; // TODO: Replace with best practice as defined by guideline }; - // Reads more data; throws if error/canceled - // return copied size + /** + * @brief Read portion of data into a buffer. + * @remark Throws if error/canceled. + * + * @param conntext #Context so that operation can be canceled. + * @param buffer Pointer to a first byte of the byte buffer to read the data into. + * @param count Size of the buffer to read the data into. + * + * @return Number of bytes read. + */ virtual int64_t Read(Context const& context, uint8_t* buffer, int64_t count) = 0; - // Keep reading until buffer is all fill out of the end of stream content is reached + /** + * @brief Read #BodyStream into a buffer until the buffer is filled, or until the stream is read + * to end. + * + * @param conntext #Context so that operation can be canceled. + * @param body #BodyStream to read. + * @param buffer Pointer to a first byte of the byte buffer to read the data into. + * @param count Size of the buffer to read the data into. + * + * @return Number of bytes read. + */ static int64_t ReadToCount( Context const& context, BodyStream& body, uint8_t* buffer, int64_t count); + /** + * @brief Read #BodyStream until the stream is read to end, allocating memory for the entirety + * of contents. + * + * @param conntext #Context so that operation can be canceled. + * @param body #BodyStream to read. + * + * @return A vector of bytes containing the entirety of data read from the \p body. + */ static std::vector ReadToEnd(Context const& context, BodyStream& body); }; + /** + * @brief #BodyStream providing data from an initialized memory buffer. + */ class MemoryBodyStream : public BodyStream { private: const uint8_t* m_data; @@ -66,12 +109,23 @@ namespace Azure { namespace Core { namespace Http { // Forbid constructor for rval so we don't end up storing dangling ptr MemoryBodyStream(std::vector const&&) = delete; + /** + * @brief Construct using vector of bytes. + * + * @param buffer Vector of bytes with the contents to provide the data from to the readers. + */ MemoryBodyStream(std::vector const& buffer) : MemoryBodyStream(buffer.data(), static_cast(buffer.size())) { } - // cast as vector from ptr and length + /** + * @brief Construct using buffer pointer and its size. + * + * @param data Pointer to a first byte of the buffer with the contents to provide the data from + * to the readers. + * @param length Size of the buffer. + */ explicit MemoryBodyStream(const uint8_t* data, int64_t length) : m_data(data), m_length(length) { } @@ -83,9 +137,13 @@ namespace Azure { namespace Core { namespace Http { void Rewind() override { m_offset = 0; } }; - // Use for request with no body + /** + * @brief Empty #BodyStream. + * @remark Used for requests with no body. + */ class NullBodyStream : public Azure::Core::Http::BodyStream { public: + /// Constructor. explicit NullBodyStream() {} int64_t Length() const override { return 0; } @@ -100,6 +158,9 @@ namespace Azure { namespace Core { namespace Http { return 0; }; + /** + * @brief Gets a singleton instance of a #NullBodyStream. + */ static NullBodyStream* GetNullBodyStream() { static NullBodyStream nullBodyStream; @@ -108,6 +169,9 @@ namespace Azure { namespace Core { namespace Http { }; #ifdef POSIX + /** + * @brief #BodyStream providing its data from a file. + */ class FileBodyStream : public BodyStream { private: // in mutable @@ -118,6 +182,13 @@ namespace Azure { namespace Core { namespace Http { int64_t m_offset; public: + /** + * @brief Construct from a file. + * + * @param fd File descriptor. + * @param offset Offset in the file to start providing the data from. + * @param length Length of the data, in bytes, to provide. + */ FileBodyStream(int fd, int64_t offset, int64_t length) : m_fd(fd), m_baseOffset(offset), m_length(length), m_offset(0) { @@ -133,6 +204,9 @@ namespace Azure { namespace Core { namespace Http { #endif #ifdef WINDOWS + /** + * @brief #BodyStream providing its data from a file. + */ class FileBodyStream : public BodyStream { private: // in mutable @@ -143,6 +217,13 @@ namespace Azure { namespace Core { namespace Http { int64_t m_offset; public: + /** + * @brief Construct from a file. + * + * @param hFile File handle. + * @param offset Offset in the file to start providing the data from. + * @param length Length of the data, in bytes, to provide. + */ FileBodyStream(HANDLE hFile, int64_t offset, int64_t length) : m_hFile(hFile), m_baseOffset(offset), m_length(length), m_offset(0) { @@ -157,6 +238,9 @@ namespace Azure { namespace Core { namespace Http { }; #endif // Windows + /** + * @brief #BodyStream that provides its data from another #BodyStream. + */ class LimitBodyStream : public BodyStream { private: BodyStream* m_inner; @@ -164,6 +248,12 @@ namespace Azure { namespace Core { namespace Http { int64_t m_bytesRead = 0; public: + /** + * @brief Construct from another #BodyStream. + * + * @param inner #BodyStream to provide the data from to the readers. + * @param max_length Maximum number of bytes to provide to the readers. + */ LimitBodyStream(BodyStream* inner, int64_t max_length) : m_inner(inner), m_length(std::min(inner->Length(), max_length)) { diff --git a/sdk/core/azure-core/inc/azure/core/http/curl/curl.hpp b/sdk/core/azure-core/inc/azure/core/http/curl/curl.hpp index fff7f74fc..3dc95a527 100644 --- a/sdk/core/azure-core/inc/azure/core/http/curl/curl.hpp +++ b/sdk/core/azure-core/inc/azure/core/http/curl/curl.hpp @@ -1,8 +1,10 @@ -#pragma once - // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +/** + * @brief #HttpTransport implementation via CURL. + */ + #pragma once #include "azure/core/http/http.hpp" @@ -41,6 +43,9 @@ namespace Azure { namespace Core { namespace Http { constexpr static int c_DefaultConnectionExpiredMilliseconds = 1000 * 60; } // namespace Details + /** + * @brief CURL HTTP connection. + */ class CurlConnection { private: CURL* m_handle; @@ -48,16 +53,40 @@ namespace Azure { namespace Core { namespace Http { std::chrono::steady_clock::time_point m_lastUseTime; public: + /** + * @Brief Construct CURL HTTP connection. + * + * @param host HTTP connection host name. + */ CurlConnection(std::string const& host) : m_handle(curl_easy_init()), m_host(host) {} + /** + * @brief Destructor. + * @detail Cleans up CURL (invokes `curl_easy_cleanup()`). + */ ~CurlConnection() { curl_easy_cleanup(this->m_handle); } + /** + * @brief Get CURL handle. + * @return CURL handle for the HTTP connection. + */ CURL* GetHandle() { return this->m_handle; } + /** + * @brief Get HTTP connection host. + * @return HTTP connection host name. + */ std::string GetHost() const { return this->m_host; } + /** + * @brief Update last usage time for the connection. + */ void updateLastUsageTime() { this->m_lastUseTime = std::chrono::steady_clock::now(); } + /** + * @brief Checks whether this CURL connection is expired. + * @return `true` if this connextion is onsidered expired, `false` oterwise. + */ bool isExpired() { auto connectionOnWaitingTimeMs = std::chrono::duration_cast( @@ -66,6 +95,9 @@ namespace Azure { namespace Core { namespace Http { } }; + /** + * CURL HTTP connection pool. + */ struct CurlConnectionPool { #ifdef TESTING_BUILD @@ -74,28 +106,36 @@ namespace Azure { namespace Core { namespace Http { friend class Azure::Core::Test::TransportAdapter_ConnectionPoolCleaner_Test; #endif - /* - * Mutex for accessing connection pool for thread-safe reading and writing + /** + * @brief Mutex for accessing connection pool for thread-safe reading and writing. */ static std::mutex s_connectionPoolMutex; - /* - * Keeps an unique key for each host and creates a connection pool for each key. - * This way getting a connection for an specific host can be done in O(1) instead of looping a - * single connection list to find the first connection for the required host. + /** + * @brief Keeps an unique key for each host and creates a connection pool for each key. * - * There might be multiple connections for each host. + * @detail This way getting a connection for an specific host can be done in O(1) instead of + * looping a single connection list to find the first connection for the required host. + * + * @remark There might be multiple connections for each host. */ static std::map>> s_connectionPoolIndex; - /* - * Finds a connection to be re-used from the connection pool. If there is not any available - * connection, a new connection is created. + /** + * @brief Finds a connection to be re-used from the connection pool. + * @remark If there is not any available connection, a new connection is created. + * + * @param request HTTP request to get #CurlConnection for. + * + * @return #CurlConnection to use. */ static std::unique_ptr GetCurlConnection(Request& request); /** - * Moves a connection back to the pool to be re-used + * @brief Moves a connection back to the pool to be re-used. + * + * @param connection CURL HTTP connection to add to the pool. + * @param lastStatusCode The most recent HTTP status code received from the \p connection. */ static void MoveConnectionBackToPool( std::unique_ptr connection, @@ -413,9 +453,19 @@ namespace Azure { namespace Core { namespace Http { * @brief Function used when working with Streams to manually write from the HTTP Request to * the wire. * + * @param context #Context so that operation can be canceled. + * * @return CURL_OK when response is sent successfully. */ CURLcode SendRawHttp(Context const& context); + + /** + * @brief Upload body. + * + * @param context #Context so that operation can be canceled. + * + * @return Curl code. + */ CURLcode UploadBody(Context const& context); /** @@ -433,6 +483,8 @@ namespace Azure { namespace Core { namespace Http { * @brief This function is used after sending an HTTP request to the server to read the HTTP * RawResponse from wire until the end of headers only. * + * @param reUseInternalBUffer Indicates whether the internal buffer should be reused. + * * @return CURL_OK when an HTTP response is created. */ void ReadStatusLineAndHeadersFromRawResponse(bool reUseInternalBUffer = false); @@ -456,8 +508,15 @@ namespace Azure { namespace Core { namespace Http { */ int64_t ReadFromSocket(uint8_t* buffer, int64_t bufferSize); + /** + * @brief Last HTTP status code read. + */ Http::HttpStatusCode m_lastStatusCode; + /** + * @brief check whether an end of file has been reached. + * @return `true` if end of file has been reached, `false` otherwise. + */ bool IsEOF() { return this->m_isChunkedResponseType ? this->m_chunkSize == 0 diff --git a/sdk/core/azure-core/inc/azure/core/http/http.hpp b/sdk/core/azure-core/inc/azure/core/http/http.hpp index 82ea2ffca..e32bbfb5a 100644 --- a/sdk/core/azure-core/inc/azure/core/http/http.hpp +++ b/sdk/core/azure-core/inc/azure/core/http/http.hpp @@ -1,6 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +/** + * @file + * @brief HTTP request and response functionality. + */ + #pragma once #include "azure/core/http/body_stream.hpp" @@ -37,99 +42,116 @@ namespace Azure { namespace Core { namespace Http { } } // namespace Details + /** + * @brief HTTP transport implementation used. + */ enum class TransportKind { // TODO move this to Factory - Curl, - WinHttp + Curl, ///< CURL. + WinHttp ///< WinHTTP. }; + /** + * @brief Defines the possible HTTP status codes. + */ enum class HttpStatusCode { + /// No HTTP status code. None = 0, - // 1xx (information) Status Codes: - Continue = 100, - SwitchingProtocols = 101, - Processing = 102, - EarlyHints = 103, + // === 1xx (information) Status Codes: === + Continue = 100, ///< HTTP 100 Continue. + SwitchingProtocols = 101, ///< HTTP 101 Switching Protocols. + Processing = 102, ///< HTTP 102 Processing. + EarlyHints = 103, ///< HTTP 103 Early Hints. - // 2xx (successful) Status Codes: - Ok = 200, - Created = 201, - Accepted = 202, - NonAuthoritativeInformation = 203, - NoContent = 204, - ResetContent = 205, - PartialContent = 206, - MultiStatus = 207, - AlreadyReported = 208, - ImUsed = 226, + // === 2xx (successful) Status Codes: === + Ok = 200, ///< HTTP 200 OK. + Created = 201, ///< HTTP 201 Created. + Accepted = 202, ///< HTTP 202 Accepted. + NonAuthoritativeInformation = 203, ///< HTTP 203 Non-Authoritative Information. + NoContent = 204, ///< HTTP 204 No Content. + ResetContent = 205, ///< HTTP 205 Rest Content. + PartialContent = 206, ///< HTTP 206 Partial Content. + MultiStatus = 207, ///< HTTP 207 Multi-Status. + AlreadyReported = 208, ///< HTTP 208 Already Reported. + IMUsed = 226, ///< HTTP 226 IM Used. - // 3xx (redirection) Status Codes: - MultipleChoices = 300, - MovedPermanently = 301, - Found = 302, - SeeOther = 303, - NotModified = 304, - UseProxy = 305, - TemporaryRedirect = 307, - PermanentRedirect = 308, + // === 3xx (redirection) Status Codes: === + MultipleChoices = 300, ///< HTTP 300 Multiple Choices. + MovedPermanently = 301, ///< HTTP 301 Moved Permanently. + Found = 302, ///< HTTP 302 Found. + SeeOther = 303, ///< HTTP 303 See Other. + NotModified = 304, ///< HTTP 304 Not Modified. + UseProxy = 305, ///< HTTP 305 Use Proxy. + TemporaryRedirect = 307, ///< HTTP 307 Temporary Redirect. + PermanentRedirect = 308, ///< HTTP 308 Permanent Redirect. - // 4xx (client error) Status Codes: - BadRequest = 400, - Unauthorized = 401, - PaymentRequired = 402, - Forbidden = 403, - NotFound = 404, - MethodNotAllowed = 405, - NotAcceptable = 406, - ProxyAuthenticationRequired = 407, - RequestTimeout = 408, - Conflict = 409, - Gone = 410, - LengthRequired = 411, - PreconditionFailed = 412, - PayloadTooLarge = 413, - UriTooLong = 414, - UnsupportedMediaType = 415, - RangeNotSatisfiable = 416, - ExpectationFailed = 417, - MisdirectedRequest = 421, - UnprocessableEntity = 422, - Locked = 423, - FailedDependency = 424, - TooEarly = 425, - UpgradeRequired = 426, - PreconditionRequired = 428, - TooManyRequests = 429, - RequestHeaderFieldsTooLarge = 431, - UnavailableForLegalReasons = 451, + // === 4xx (client error) Status Codes: === + BadRequest = 400, ///< HTTP 400 Bad Request. + Unauthorized = 401, ///< HTTP 401 Unauthorized. + PaymentRequired = 402, ///< HTTP 402 Payment Required. + Forbidden = 403, ///< HTTP 403 Forbidden. + NotFound = 404, ///< HTTP 404 Not Found. + MethodNotAllowed = 405, ///< HTTP 405 Method Not Allowed. + NotAcceptable = 406, ///< HTTP 406 Not Acceptable. + ProxyAuthenticationRequired = 407, ///< HTTP 407 Proxy Authentication Required. + RequestTimeout = 408, ///< HTTP 408 Request Timeout. + Conflict = 409, ///< HTTP 409 Conflict. + Gone = 410, ///< HTTP 410 Gone. + LengthRequired = 411, ///< HTTP 411 Length Required. + PreconditionFailed = 412, ///< HTTP 412 Precondition Failed. + PayloadTooLarge = 413, ///< HTTP 413 Payload Too Large. + UriTooLong = 414, ///< HTTP 414 URI Too Long. + UnsupportedMediaType = 415, ///< HTTP 415 Unsupported Media Type. + RangeNotSatisfiable = 416, ///< HTTP 416 Range Not Satisfiable. + ExpectationFailed = 417, ///< HTTP 417 Expectation Failed. + MisdirectedRequest = 421, ///< HTTP 421 Misdirected Request. + UnprocessableEntity = 422, ///< HTTP 422 Unprocessable Entity. + Locked = 423, ///< HTTP 423 Locked. + FailedDependency = 424, ///< HTTP 424 Failed Dependency. + TooEarly = 425, ///< HTTP 425 Too Early. + UpgradeRequired = 426, ///< HTTP 426 Upgrade Required. + PreconditionRequired = 428, ///< HTTP 428 Precondition Required. + TooManyRequests = 429, ///< HTTP 429 Too Many Requests. + RequestHeaderFieldsTooLarge = 431, ///< HTTP 431 Request Header Fields Too Large. + UnavailableForLegalReasons = 451, ///< HTTP 451 Unavailable For Legal Reasons. - // 5xx (server error) Status Codes: - InternalServerError = 500, - NotImplemented = 501, - BadGateway = 502, - ServiceUnavailable = 503, - GatewayTimeout = 504, - HttpVersionNotSupported = 505, - VariantAlsoNegotiates = 506, - InsufficientStorage = 507, - LoopDetected = 508, - NotExtended = 510, - NetworkAuthenticationRequired = 511, + // === 5xx (server error) Status Codes: === + InternalServerError = 500, ///< HTTP 500 Internal Server Error. + NotImplemented = 501, ///< HTTP 501 Not Implemented. + BadGateway = 502, ///< HTTP 502 Bad Gateway. + ServiceUnavailable = 503, ///< HTTP 503 Unavailable. + GatewayTimeout = 504, ///< HTTP 504 Gateway Timeout. + HttpVersionNotSupported = 505, ///< HTTP 505 HTTP Version Not Supported. + VariantAlsoNegotiates = 506, ///< HTTP 506 Variant Also Negotiates. + InsufficientStorage = 507, ///< HTTP 507 Insufficient Storage. + LoopDetected = 508, ///< HTTP 508 Loop Detected. + NotExtended = 510, ///< HTTP 510 Not Extended. + NetworkAuthenticationRequired = 511, ///< HTTP 511 Network Authentication Required. }; + /** + * HTTP request method. + */ enum class HttpMethod { - Get, - Head, - Post, - Put, - Delete, - Patch, + Get, ///< GET + Head, ///< HEAD + Post, ///< POST + Put, ///< PUT + Delete, ///< DELETE + Patch, ///< PATCH }; + /** + * @brief Get a string representation for a value of #HttpMethod. + * + * @param method A value of #HttpMethod value. + * + * @return String name that corresponds to a value of #HttpMethod type. + */ inline std::string HttpMethodToString(const HttpMethod& method) { switch (method) @@ -151,15 +173,21 @@ namespace Azure { namespace Core { namespace Http { } } + /** + * Type of HTTP response body. + */ enum class BodyType { - Buffer, - Stream, + Buffer, ///< Buffer. + Stream, ///< Stream. }; // Url represent the location where a request will be performed. It can be parsed and init from // a string that contains all Url parts (scheme, host, path, etc). // Authority is not currently supported. + /** + * @brief URL. + */ class Url { // Let Request class to be able to set retry enabled ON friend class Request; @@ -198,39 +226,76 @@ namespace Azure { namespace Core { namespace Http { } public: - // Create empty Url instance. Usually for building Url from scratch + /** + * @brief Construct an empty URL. + */ Url() {} - // Create Url from an url-encoded string. Usually from pre-built url with query parameters (like - // SaS) url is expected to be already url-encoded. + /** + * @brief Construct a URL from a URL-encoded string. + * + * @param encodedUrl URL string that has all its expected parts already URL-encoded. + */ explicit Url(const std::string& encodedUrl); /************* Builder Url functions ****************/ /******** API for building Url from scratch. Override state ********/ + /** + * @brief Set URL scheme. + * + * @param scheme URL scheme. + */ void SetScheme(const std::string& scheme) { m_scheme = scheme; } + /** + * @brief Set URL host. + * + * @param host URL host. + * @param isHostEncoded `true` if \p host is URL-encoded, `false` otherwise. + */ void SetHost(const std::string& host, bool isHostEncoded = false) { m_host = isHostEncoded ? host : EncodeHost(host); } + /** + * @brief Set URL port. + * + * @param port URL port. + */ void SetPort(uint16_t port) { m_port = port; } + /** + * @brief Set URL path. + * + * @param path URL path. + * @param isPathEncoded `true` if \p fragment is URL-encoded, `false` otherwise. + */ void SetPath(const std::string& path, bool isPathEncoded = false) { m_encodedPath = isPathEncoded ? path : EncodePath(path); } + /** + * @brief Set URL fragment. + * + * @param fragment URL fragment. + * @param isFragmentEncoded `true` if \p fragment is URL-encoded, `false` otherwise. + */ void SetFragment(const std::string& fragment, bool isFragmentEncoded = false) { m_fragment = isFragmentEncoded ? fragment : EncodeFragment(fragment); } - /******** API for mutating Url state ********/ + // ===== APIs for mutating URL state: ====== - // Path is mostly expected to be appended without url-encoding. Be default, path will be encoded - // before it is added to Url. \p isPathEncoded can set to true to avoid encoding. + /** + * @brief Append an element of URL path. + * + * @param path URL path element to append. + * @param isPathEncoded `true` if \p path is URL-encoded, `false` otherwise. + */ void AppendPath(const std::string& path, bool isPathEncoded = false) { if (!m_encodedPath.empty() && m_encodedPath.back() != '/') @@ -240,13 +305,16 @@ namespace Azure { namespace Core { namespace Http { m_encodedPath += isPathEncoded ? path : EncodePath(path); } - // the value from query parameter is mostly expected to be non-url-encoded and it will be - // encoded before adding to url by default. Use \p isValueEncoded = true when the value is - // already encoded. - // - // Note: a query key can't contain any chars that needs to be url-encoded. (by RFC). - // - // Note: AppendQuery override previous query parameters. + /** + * @brief Append an HTTP query parameter. + * + * @note Overrides previous query parameters. + * @remark A query key can't contain any characters that need to be URL-encoded (per RFC). + * + * @param key HTTP query parameter. + * @param value HTTP quary parameter value. + * @param isValueEncoded `true` if \p value is URL-encoded, `false` otherwise. + */ void AppendQuery(const std::string& key, const std::string& value, bool isValueEncoded = false) { std::string encoded_value = isValueEncoded ? Decode(value) : value; @@ -260,10 +328,20 @@ namespace Azure { namespace Core { namespace Http { } } - // query must be encoded. + /** + * @brief Append a HTTP query. + * + * @note All the required HTTP query parts should be URL-encoded. + * + * @param encodedQueries HTTP query. + */ void AppendQueries(const std::string& encodedQueries); - // removes a query parameter + /** + * @brief Removes a HTTP query parameter. + * + * @param key HTTP query parameter to remove. + */ void RemoveQuery(const std::string& key) { m_queryParameters.erase(key); @@ -271,27 +349,45 @@ namespace Azure { namespace Core { namespace Http { } /************** API to read values from Url ***************/ - + /** + * @brief Get URL host. + */ std::string GetHost() const { return m_host; } + /** + * @brief Get URL path. + */ const std::string& GetPath() const { return m_encodedPath; } - // Copy from query parameters list. Query parameters from retry map have preference and will - // override any value from the initial query parameters from the request - // - // Note: Query values added with url-encoding will be encoded in the list. No decoding is done - // on values. + /** + * @brief Get all the query paramters in the URL. + * + * @note Retry parameters have preference and will override any value from the initial query + * parameters. + * @note All the values are URL-encoded. + * + * @return URL query parameters. + */ const std::map GetQuery() const { return Details::MergeMaps(m_retryQueryParameters, m_queryParameters); } + /** + * @brief Get relative URL. + */ std::string GetRelativeUrl() const; - // Url with encoded query parameters + /** + * @brief Get relative URL. + * @remark All the query parameters are URL-encoded. + */ std::string GetAbsoluteUrl() const; }; + /** + * @brief HTTP request. + */ class Request { friend class RetryPolicy; #ifdef TESTING_BUILD @@ -321,17 +417,39 @@ namespace Azure { namespace Core { namespace Http { void StopRetry(); // only called by retry policy public: + /** + * @brief Construct an HTTP request. + * + * @param httpMethod HTTP method. + * @param url URL. + * @param bodyStream HTTP #BodyStream. + * @param downloadViaStream + */ explicit Request(HttpMethod httpMethod, Url url, BodyStream* bodyStream, bool downloadViaStream) : m_method(std::move(httpMethod)), m_url(std::move(url)), m_bodyStream(bodyStream), m_retryModeEnabled(false), m_isDownloadViaStream(downloadViaStream) { } + /** + * @brief Construct an HTTP request. + * + * @param httpMethod HTTP method. + * @param url URL. + * @param bodyStream HTTP #BodyStream. + */ explicit Request(HttpMethod httpMethod, Url url, BodyStream* bodyStream) : Request(httpMethod, std::move(url), bodyStream, false) { } + /** + * @brief Construct an HTTP request. + * + * @param httpMethod HTTP method. + * @param url URL. + * @param downloadViaStream + */ // Typically used for GET with no request body that can return bodyStream explicit Request(HttpMethod httpMethod, Url url, bool downloadViaStream) : Request( @@ -342,41 +460,105 @@ namespace Azure { namespace Core { namespace Http { { } + /** + * @brief Construct an HTTP request. + * + * @param httpMethod HTTP method. + * @param url URL. + */ // Typically used for GET with no request body. explicit Request(HttpMethod httpMethod, Url url) : Request(httpMethod, std::move(url), NullBodyStream::GetNullBodyStream(), false) { } + /** + * @brief Add an HTTP header. + * + * @param name HTTP header name. + * @param value HTTP header value. + */ void AddHeader(std::string const& name, std::string const& value); + + /** + * @brief Remove an HTTP header. + * + * @param name HTTP header name. + */ void RemoveHeader(std::string const& name); + + /** + * @brief Set upload chunk size. + * + * @param size Upload chunk size. + */ void SetUploadChunkSize(int64_t size) { this->m_uploadChunkSize = size; } // Methods used by transport layer (and logger) to send request + /** + * @brief Get HTTP method. + */ HttpMethod GetMethod() const; + + /** + * @brief Get HTTP headers. + */ std::map GetHeaders() const; + + /** + * @brief Get HTTP body as #BodyStream. + */ BodyStream* GetBodyStream() { return this->m_bodyStream; } + + /** + * @brief Get HTTP message prior to HTTP body. + */ std::string GetHTTPMessagePreBody() const; + + /** + * @brief Get upload chunk size. + */ int64_t GetUploadChunkSize() { return this->m_uploadChunkSize; } + + /** + * @brief + */ bool IsDownloadViaStream() { return this->m_isDownloadViaStream; } + + /** + * @brief Get URL. + */ Url& GetUrl() { return this->m_url; } + + /** + * @brief Get URL. + */ Url const& GetUrl() const { return this->m_url; } }; /* * RawResponse exceptions */ + /** + * @brief Couldn't resolve HTTP host. + */ struct CouldNotResolveHostException : public std::runtime_error { explicit CouldNotResolveHostException(std::string const& msg) : std::runtime_error(msg) {} }; - // Any other excpetion from transport layer without an specific exception defined above + // Any other exception from transport layer without an specific exception defined above + /** + * @brief HTTP transport layer error. + */ struct TransportException : public std::runtime_error { explicit TransportException(std::string const& msg) : std::runtime_error(msg) {} }; + /** + * @brief Raw HTTP response. + */ class RawResponse { private: @@ -401,6 +583,14 @@ namespace Azure { namespace Core { namespace Http { } public: + /** + * @brief Construct raw HTTP response. + * + * @param majorVersion HTTP protocol version major number. + * @param minorVersion HTTP protocol version minor number. + * @param statusCode HTTP status code. + * @param reasonPhrase HTP reason phrase. + */ explicit RawResponse( int32_t majorVersion, int32_t minorVersion, @@ -410,27 +600,92 @@ namespace Azure { namespace Core { namespace Http { { } - // Methods used to build HTTP response + // ===== Methods used to build HTTP response ===== + + /** + * @brief Add header to the HTTP response. + * + * @param name HTTP response header name. + * @param value HTTP response header value. + */ void AddHeader(std::string const& name, std::string const& value); + + /** + * @brief Add header to the HTTP response. + * + * @param header HTTP response header in RFCnnnn format (`OWS header-value OWS`). + */ // rfc form header-name: OWS header-value OWS void AddHeader(std::string const& header); + + /** + * @brief Add header to the HTTP response. + * @detail HTTP response header should be in RFCnnnn format (`OWS header-value OWS`). + * + * @param begin Pointer to the first byte of the header string in RFCnnnn format. + * @param last Pointer to the last byte of the header string in RFCnnnn format. + */ void AddHeader(uint8_t const* const begin, uint8_t const* const last); + + /** + * @brief Set #BodyStream for this HTTP response. + * + * @param stream HTTP #BodyStream. + */ void SetBodyStream(std::unique_ptr stream); + + /** + * @brief Set HTTP response body for this HTTP response. + * + * @param body HTTP response body bytes. + */ void SetBody(std::vector body) { this->m_body = std::move(body); } // adding getters for version and stream body. Clang will complain on Mac if we have unused // fields in a class + + /** + * @brief Get major number of the HTTP response protocol version. + */ int32_t GetMajorVersion() const { return this->m_majorVersion; } + + /** + * @brief Get minor number of the HTTP response protocol version. + */ int32_t GetMinorVersion() const { return this->m_minorVersion; } + + /** + * @brief Get HTTP status code of the HTTP response. + */ HttpStatusCode GetStatusCode() const; + + /** + * @brief Get HTTP reason phrase code of the HTTP response. + */ std::string const& GetReasonPhrase() const; + + /** + * @brief Get HTTP response headers. + */ std::map const& GetHeaders() const; + + /** + * @brief Get HTTP response body as #BodyStream. + */ std::unique_ptr GetBodyStream() { // If m_bodyStream was moved before. nullpr is returned return std::move(this->m_bodyStream); } + + /** + * @brief Get HTTP response body as vector of bytes. + */ std::vector& GetBody() { return this->m_body; } + + /** + * @brief Get HTTP response body as vector of bytes. + */ std::vector const& GetBody() const { return this->m_body; } }; diff --git a/sdk/core/azure-core/inc/azure/core/http/pipeline.hpp b/sdk/core/azure-core/inc/azure/core/http/pipeline.hpp index 6f0da8c87..6a50dcf0f 100644 --- a/sdk/core/azure-core/inc/azure/core/http/pipeline.hpp +++ b/sdk/core/azure-core/inc/azure/core/http/pipeline.hpp @@ -1,6 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +/** + * @file + * @brief HTTP pipeline is a stack of HTTP policies. + * @remark See #policy.hpp + */ + #pragma once #include "azure/core/context.hpp" @@ -12,11 +18,26 @@ namespace Azure { namespace Core { namespace Http { + /** + * @brief HTTP pipeline is a stack of HTTP policies that get applied sequentially. + * + * @details Every client is expected to have its own HTTP pipeline, consisting of sequence of + * individual HTTP policies. Policies shape the behavior of how a HTTP request is being handled, + * ranging from retrying and logging, up to sending a HTTP request over the wire. + * + * @remark See #policy.hpp + */ class HttpPipeline { protected: std::vector> m_policies; public: + /** + * @brief Construct HTTP pipeline with the sequence of HTTP policies provided. + * + * @param policies A sequence of #HttpPolicy representing a stack, first element corresponding + * to the top of the stack. + */ explicit HttpPipeline(const std::vector>& policies) { m_policies.reserve(policies.size()); @@ -26,11 +47,18 @@ namespace Azure { namespace Core { namespace Http { } } + /** + * @brief Construct HTTP pipeline with the sequence of HTTP policies provided. + * + * @param policies A sequence of #HttpPolicy representing a stack, first element corresponding + * to the top of the stack. + */ explicit HttpPipeline(std::vector>&& policies) : m_policies(std::move(policies)) { } + /// Copy constructor. HttpPipeline(const HttpPipeline& other) { m_policies.reserve(other.m_policies.size()); @@ -41,11 +69,12 @@ namespace Azure { namespace Core { namespace Http { } /** - * @brief Starts the pipeline - * @param ctx A cancellation token. Can also be used to provide overrides to individual - * policies - * @param request The request to be processed - * @return unique_ptr + * @brief Start the HTTP pipeline. + * + * @param ctx #Context so that operation can be canceled. + * @param request The HTTP request to be processed. + * + * @return HTTP response after the request has been processed. */ std::unique_ptr Send(Context const& ctx, Request& request) const { diff --git a/sdk/core/azure-core/inc/azure/core/http/policy.hpp b/sdk/core/azure-core/inc/azure/core/http/policy.hpp index 8e68ae3b5..451eaa0f6 100644 --- a/sdk/core/azure-core/inc/azure/core/http/policy.hpp +++ b/sdk/core/azure-core/inc/azure/core/http/policy.hpp @@ -1,6 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +/** + * @file + * @brief Utilities to be used by HTTP transport policy implementations. + */ + #pragma once #include "azure/core/azure.hpp" @@ -17,16 +22,38 @@ namespace Azure { namespace Core { namespace Http { class NextHttpPolicy; + /** + * @brief HTTP policy. + * An HTTP pipeline inside SDK clients is an stack sequence of HTTP policies. + */ class HttpPolicy { public: // If we get a response that goes up the stack // Any errors in the pipeline throws an exception // At the top of the pipeline we might want to turn certain responses into exceptions + + /** + * @brief Apply this HTTP policy. + * + * @param context #Context so that operation can be canceled. + * @param request An HTTP #Request being sent. + * @param policy #NextHttpPolicy to invoke after this policy has been applied. + * + * @return An HTTP #RawResponse after this policy, and all subsequent HTTP policies in the stack + * sequence of policies have been applied. + */ virtual std::unique_ptr Send( Context const& context, Request& request, NextHttpPolicy policy) const = 0; + + /// Destructor. virtual ~HttpPolicy() {} + + /** + * @brief Creates a clone of this HTTP policy. + * @return A clone of this HTTP policy. + */ virtual std::unique_ptr Clone() const = 0; protected: @@ -36,11 +63,20 @@ namespace Azure { namespace Core { namespace Http { HttpPolicy& operator=(const HttpPolicy& other) = default; }; + // Represents the next HTTP policy in the stack sequence of policies. class NextHttpPolicy { const std::size_t m_index; const std::vector>* m_policies; public: + /** + * @brief Construct an abstraction representing a next line in the stack sequence of policies, + * from the caller's perspective. + * + * @param index An sequential index of this policy in the stack sequence of policies. + * @param policies A vector of unique pointers next in the line to be invoked after the current + * policy. + */ explicit NextHttpPolicy( std::size_t index, const std::vector>* policies) @@ -48,14 +84,33 @@ namespace Azure { namespace Core { namespace Http { { } + /** + * @brief Apply this HTTP policy. + * + * @param context #Context so that operation can be canceled. + * @param request An HTTP #Request being sent. + * + * @return An HTTP #RawResponse after this policy, and all subsequent HTTP policies in the stack + * sequence of policies have been applied. + */ std::unique_ptr Send(Context const& ctx, Request& req); }; + /** + * @brief Applying this policy sends an HTTP request over the wire. + * @remark This policy must be the bottom policy in the stack of the HTTP policy stack. + */ class TransportPolicy : public HttpPolicy { private: std::shared_ptr m_transport; public: + /** + * @brief Construct an HTTP transport policy. + * + * @param transport A pointer to the #HttpTransport implementation to use when this policy gets + * applied (#Send). + */ explicit TransportPolicy(std::shared_ptr transport) : m_transport(std::move(transport)) { @@ -72,13 +127,29 @@ namespace Azure { namespace Core { namespace Http { NextHttpPolicy nextHttpPolicy) const override; }; + /** + * @brief Options for the #RetryPolicy. + */ struct RetryOptions { + /** + * @brief Maximum number of attempts to retry. + */ int MaxRetries = 3; + /** + * @brief Mimimum amount of time between retry attempts. + */ std::chrono::milliseconds RetryDelay = std::chrono::seconds(4); + + /** + * @brief Mimimum amount of time between retry attempts. + */ decltype(RetryDelay) MaxRetryDelay = std::chrono::minutes(2); + /** + * @brief HTTP status codes to retry on. + */ std::vector StatusCodes{ HttpStatusCode::RequestTimeout, HttpStatusCode::InternalServerError, @@ -88,11 +159,19 @@ namespace Azure { namespace Core { namespace Http { }; }; + /** + * @brief HTTP retry policy. + */ class RetryPolicy : public HttpPolicy { private: RetryOptions m_retryOptions; public: + /** + * Constructs HTTP retry policy with the provided #RetryOptions. + * + * @param options HTTP #RetryOptions. + */ explicit RetryPolicy(RetryOptions options) : m_retryOptions(std::move(options)) {} std::unique_ptr Clone() const override @@ -106,11 +185,20 @@ namespace Azure { namespace Core { namespace Http { NextHttpPolicy nextHttpPolicy) const override; }; + /** + * @brief HTTP Request ID policy. + * + * @details Applies an HTTP header with a unique ID to each HTTP request, so that each individual + * request can be traced for troubleshooting. + */ class RequestIdPolicy : public HttpPolicy { private: constexpr static const char* RequestIdHeader = "x-ms-client-request-id"; public: + /** + * @brief Constructs HTTP request ID policy. + */ explicit RequestIdPolicy() {} std::unique_ptr Clone() const override @@ -130,6 +218,13 @@ namespace Azure { namespace Core { namespace Http { } }; + /** + * @brief HTTP telemetry policy. + * + * @details Applies an HTTP header with a component name and version to each HTTP request, + * includes Azure SDK version information, and operating system information. + * @remark See https://azure.github.io/azure-sdk/general_azurecore.html#telemetry-policy. + */ class TelemetryPolicy : public HttpPolicy { std::string m_telemetryId; @@ -141,11 +236,25 @@ namespace Azure { namespace Core { namespace Http { std::string const& applicationId); public: + /** + * @brief Construct HTTP telemetry policy with component name and component version. + * + * @param componentName Azure SDK component name (e.g. "storage.blobs") + * @param componentVersion Azure SDK component version (e.g. "11.0.0") + */ explicit TelemetryPolicy(std::string const& componentName, std::string const& componentVersion) : TelemetryPolicy(componentName, componentVersion, g_emptyApplicationId) { } + /** + * @brief Construct HTTP telemetry policy with component name, component version, and an + * applicatin ID. + * + * @param componentName Azure SDK component name (e.g. "storage.blobs") + * @param componentVersion Azure SDK component version (e.g. "11.0.0") + * @param applicationId Customer Application ID (e.g. "AzCopy") + */ explicit TelemetryPolicy( std::string const& componentName, std::string const& componentVersion, @@ -165,8 +274,17 @@ namespace Azure { namespace Core { namespace Http { NextHttpPolicy nextHttpPolicy) const override; }; + /** + * @brief Logs every HTTP request. + * + * @detail Logs every HTTP request, response, or retry attempt (see #LogClassification) + * @remark See #logging.hpp + */ class LoggingPolicy : public HttpPolicy { public: + /** + * @brief Constructs HTTP logging policy. + */ explicit LoggingPolicy() {} std::unique_ptr Clone() const override @@ -180,11 +298,20 @@ namespace Azure { namespace Core { namespace Http { NextHttpPolicy nextHttpPolicy) const override; }; + /** + * @brief Log classigications being used to designate log messages from HTTP #LoggingPolicy. + */ class LogClassification : private Azure::Core::Logging::Details::LogClassificationProvider< Azure::Core::Logging::Details::Facility::Core> { public: + /// HTTP request. static constexpr auto const Request = Classification(1); + + /// HTTP response. static constexpr auto const Response = Classification(2); + + /// HTTP retry attempt. static constexpr auto const Retry = Classification(3); }; + }}} // namespace Azure::Core::Http diff --git a/sdk/core/azure-core/inc/azure/core/http/transport.hpp b/sdk/core/azure-core/inc/azure/core/http/transport.hpp index a36bfc3af..0a22e785c 100644 --- a/sdk/core/azure-core/inc/azure/core/http/transport.hpp +++ b/sdk/core/azure-core/inc/azure/core/http/transport.hpp @@ -1,6 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +/** + * @file + * @brief Utilities to be used by HTTP transport implementations. + */ + #pragma once #include "azure/core/context.hpp" @@ -8,14 +13,25 @@ namespace Azure { namespace Core { namespace Http { + /** + * @brief Base class for all HTTP transport implementaions. + */ class HttpTransport { public: // If we get a response that goes up the stack // Any errors in the pipeline throws an exception // At the top of the pipeline we might want to turn certain responses into exceptions + /** + * @brief Send an HTTP request over the wire. + * + * @param context #Context so that operation can be canceled. + * @param request An HTTP #Request to send. + */ // TODO - Should this be const virtual std::unique_ptr Send(Context const& context, Request& request) = 0; + + /// Destructor. virtual ~HttpTransport() {} protected: diff --git a/sdk/core/azure-core/inc/azure/core/http/winhttp/win_http_client.hpp b/sdk/core/azure-core/inc/azure/core/http/winhttp/win_http_client.hpp index 0fd8779e8..487edc521 100644 --- a/sdk/core/azure-core/inc/azure/core/http/winhttp/win_http_client.hpp +++ b/sdk/core/azure-core/inc/azure/core/http/winhttp/win_http_client.hpp @@ -1,8 +1,10 @@ -#pragma once - // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +/** + * @brief #HttpTransport implementation via WinHttp. + */ + #pragma once #include "azure/core/http/http.hpp" @@ -13,13 +15,17 @@ namespace Azure { namespace Core { namespace Http { + /** + * @brief #HttpTransport implementation via WinHttp. + */ class WinHttpTansport : public HttpTransport { private: public: + /// Constructor. WinHttpTansport(); - ~WinHttpTansport(); + ~WinHttpTansport() override; - virtual std::unique_ptr Send(Context const& context, Request& request); + virtual std::unique_ptr Send(Context const& context, Request& request) override; }; }}} // namespace Azure::Core::Http diff --git a/sdk/core/azure-core/inc/azure/core/logging/logging.hpp b/sdk/core/azure-core/inc/azure/core/logging/logging.hpp index 5b48f3c94..0f5542943 100644 --- a/sdk/core/azure-core/inc/azure/core/logging/logging.hpp +++ b/sdk/core/azure-core/inc/azure/core/logging/logging.hpp @@ -1,6 +1,12 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +/** + * @file + * @brief This header defines the types and functions your application uses to be notified of Azure + * SDK client library log messages. + */ + #pragma once #include "azure/core/azure.hpp" @@ -15,10 +21,31 @@ namespace Azure { namespace Core { namespace Logging { class LogClassification; class LogClassifications; + /** + * @brief Defines the signature of the callback function that application developers must write in + * order to receive Azure SDK log messages. + * + * @param classification The log message classification. + * @param classification The log message. + */ typedef std::function LogListener; + /** + * @brief Set the function that will be invoked to report an SDK log message. + * + * @param logListener A #LogListener function that will be invoked when the SDK reports a log + * message matching one of the log classifications passed to #SetLogClassifications(). If null, no + * function will be invoked. + */ void SetLogListener(LogListener logListener); + + /** + * @brief Allows the application to specify which log classification types it is interested in + * receiving. + * + * @param logClassifications Log classification values. + */ void SetLogClassifications(LogClassifications logClassifications); namespace Details { @@ -33,6 +60,9 @@ namespace Azure { namespace Core { namespace Logging { class LogClassificationsPrivate; } // namespace Details + /** + * @brief Represents a set of log classifications. + */ class LogClassifications { friend class Details::LogClassificationsPrivate; @@ -42,17 +72,28 @@ namespace Azure { namespace Core { namespace Logging { explicit LogClassifications(bool all) : m_all(all) {} public: + /** + * @brief Initialize the list of log classifications with `std::initializer_list`. + * @param list An initializer list. + */ LogClassifications(std::initializer_list list) : m_classifications(list), m_all(false) { } + /** + * @brief Initialize the list of log classifications with `std::set`. + * @param set A set of classifications. + */ explicit LogClassifications(std::set set) : m_classifications(std::move(set)), m_all(false) { } }; + /** + * @brief Represents a log classification. + */ class LogClassification { template friend class Details::LogClassificationProvider; friend struct std::less; @@ -70,17 +111,34 @@ namespace Azure { namespace Core { namespace Logging { } public: + /** + * @brief Compare log classification to another one. + * @param other Another log classification to compare to. + * @return `true` if this log classification equals to \p other, `false` otherwise. + */ constexpr bool operator==(LogClassification const& other) const { return m_value == other.m_value; } + /** + * @brief Compare log classification to another one. + * @param other Another log classification to compare to. + * @return `true` if this log classification does not equal to \p other, `false` otherwise. + */ constexpr bool operator!=(LogClassification const& other) const { return m_value != other.m_value; } + /** + * @brief Represents a list of all classifications. + */ static LogClassifications const All; + + /** + * @brief Represents an empty list of classifications. + */ static LogClassifications const None; }; diff --git a/sdk/core/azure-core/inc/azure/core/nullable.hpp b/sdk/core/azure-core/inc/azure/core/nullable.hpp index 082e39fde..c2492fc66 100644 --- a/sdk/core/azure-core/inc/azure/core/nullable.hpp +++ b/sdk/core/azure-core/inc/azure/core/nullable.hpp @@ -1,6 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +/** + * @file + * @brief Manages an optional contained value, i.e. a value that may or may not be present. + */ + #pragma once #include // for abort @@ -16,6 +21,11 @@ namespace Azure { namespace Core { }; } // namespace Details + /** + * @brief Manages an optional contained value, i.e. a value that may or may not be present. + * + * @tparam T A type to represent contained values. + */ template class Nullable { union { @@ -27,9 +37,19 @@ namespace Azure { namespace Core { bool m_hasValue; public: + /** + * @brief Construct a #Nullable that represents the absence of value. + */ constexpr Nullable() : m_disengaged{}, m_hasValue(false) {} + + /** + * @brief Construct a #Nullable having an \p initialValue. + * + * @param initialValue A non-absent value to initialize with. + */ constexpr Nullable(const T& initialValue) : m_value(initialValue), m_hasValue(true) {} + /// Copy constructor. Nullable(const Nullable& other) noexcept(std::is_nothrow_copy_constructible::value) : m_hasValue(other.m_hasValue) { @@ -39,6 +59,7 @@ namespace Azure { namespace Core { } } + /// Move constructor. Nullable(Nullable&& other) noexcept(std::is_nothrow_move_constructible::value) : m_hasValue(other.m_hasValue) { @@ -48,6 +69,9 @@ namespace Azure { namespace Core { } } + /** + * @brief Destroy the contained value, if there is one. + */ ~Nullable() { if (m_hasValue) @@ -56,6 +80,9 @@ namespace Azure { namespace Core { } } + /** + * @brief Destroy any contained value, if there is one. + */ void Reset() noexcept /* enforces termination */ { if (m_hasValue) @@ -65,6 +92,11 @@ namespace Azure { namespace Core { } } + /** + * @brief Exchange the contents. + * + * @param other An instance to exchange the contents with. + */ // this assumes that swap can't throw if T is nothrow move constructible because // is_nothrow_swappable is added in C++17 void Swap(Nullable& other) noexcept(std::is_nothrow_move_constructible::value) @@ -91,15 +123,19 @@ namespace Azure { namespace Core { } } - // Intentionally lowercase to follow the Swappable requirements - // https://en.cppreference.com/w/cpp/named_req/Swappable - // + /** + * @brief Invokes #Swap while having a lowercase name that satisfies `swappable` requirements + * (see details). + * + * @details Swappable requirements: https://en.cppreference.com/w/cpp/named_req/Swappable + */ friend void swap(Nullable& lhs, Nullable& rhs) noexcept( std::is_nothrow_move_constructible::value) { lhs.Swap(rhs); } + /// Assignment operator. Nullable& operator=(const Nullable& other) { // this copy and swap may be inefficient for some Ts but @@ -108,6 +144,7 @@ namespace Azure { namespace Core { return *this; } + /// Assignment operator with move semantics. Nullable& operator=(Nullable&& other) noexcept(std::is_nothrow_move_constructible::value) { // this move and swap may be inefficient for some Ts but @@ -116,6 +153,13 @@ namespace Azure { namespace Core { return *this; } + /** + * @brief Assignment operator from another type. + * + * @tparam U + * + * @param other + */ template < class U = T, typename std::enable_if< @@ -148,6 +192,12 @@ namespace Azure { namespace Core { return *this; } + /** + * @brief Construct the contained value in-place. + * + * @detail If this instance already contains a value before the call, the contained value is + * destroyed by calling its destructor. + */ template T& Emplace(U&&... Args) noexcept(std::is_nothrow_constructible::value) { @@ -156,8 +206,16 @@ namespace Azure { namespace Core { return m_value; } + /** + * @brief Check whether a value is contained. + * + * @return `true` If a value is contained, `false` if value is absent. + */ bool HasValue() const noexcept { return m_hasValue; } + /** + * @brief Get the contained value. + */ const T& GetValue() const& noexcept { if (!m_hasValue) @@ -170,6 +228,9 @@ namespace Azure { namespace Core { return m_value; } + /** + * @brief Get the contained value reference. + */ T& GetValue() & noexcept { if (!m_hasValue) @@ -182,6 +243,9 @@ namespace Azure { namespace Core { return m_value; } + /** + * @brief Get the contained value (as rvalue reference). + */ T&& GetValue() && noexcept { if (!m_hasValue) @@ -194,14 +258,21 @@ namespace Azure { namespace Core { return std::move(m_value); } + /** + * @brief `operator bool` on the condition of #HasValue. + */ explicit operator bool() const noexcept { return HasValue(); } + /** + * @brief Get the contained value, returns \p other if value is absent. + * @param other A value to return when no value is contained. + * @return A contained value (when present), or \p other. + */ template < class U = T, typename std::enable_if< - std::is_convertible< - const T&, - typename std::remove_cv::type>::value && std::is_convertible::value, + std::is_convertible::type>::value + && std::is_convertible::value, int>::type = 0> constexpr typename std::remove_cv::type ValueOr(U&& other) const& @@ -214,12 +285,16 @@ namespace Azure { namespace Core { return static_cast::type>(std::forward(other)); } + /** + * @brief Get the contained value, returns \p other if value is absent. + * @param other A value to return when no value is contained. + * @return A contained value (when present), or \p other. + */ template < class U = T, typename std::enable_if< - std::is_convertible< - T, - typename std::remove_cv::type>::value && std::is_convertible::value, + std::is_convertible::type>::value + && std::is_convertible::value, int>::type = 0> constexpr typename std::remove_cv::type ValueOr(U&& other) && diff --git a/sdk/core/azure-core/inc/azure/core/response.hpp b/sdk/core/azure-core/inc/azure/core/response.hpp index 619c77356..c8770f5ac 100644 --- a/sdk/core/azure-core/inc/azure/core/response.hpp +++ b/sdk/core/azure-core/inc/azure/core/response.hpp @@ -1,33 +1,72 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +/** + * @file + * @brief Wraps raw HTTP response into a response of a specific type. + */ + #pragma once #include namespace Azure { namespace Core { - + /** + * @brief Wraps raw HTTP response into a response of a specific type. + * + * @tparam T A specific type of value to get from the raw HTTP response type. + */ template class Response { T m_value; std::unique_ptr m_rawResponse; public: + /** + * @brief Initialize a #Response with an initial value. + * + * @param initialValue Initial value. + * @param rawResponse Raw HTTP response. + */ // Require a raw response to create a Response T explicit Response(T initialValue, std::unique_ptr&& rawResponse) : m_value(std::move(initialValue)), m_rawResponse(std::move(rawResponse)) { } + /** + * @brief Get raw HTTP response. + */ // Do not give up raw response ownership. Http::RawResponse& GetRawResponse() { return *this->m_rawResponse; } + /** + * @brief Get a pointer to a value of a specific type. + */ const T* operator->() const { return &this->m_value; }; + + /** + * @brief Get a tpointer to a value of a specific type. + */ T* operator->() { return &this->m_value; }; + + /** + * @brief Get value of a specific type. + */ T& operator*() { return this->m_value; }; + + /** + * @brief Get value of a specific type. + */ const T& operator*() const { return this->m_value; }; + /** + * @brief Get an rvalue reference to the value of a specific type. + */ T&& ExtractValue() { return std::move(this->m_value); } + /** + * @brief Get a smaprt pointer rvalue reference to the value of a specific type. + */ std::unique_ptr&& ExtractRawResponse() { return std::move(this->m_rawResponse); diff --git a/sdk/core/azure-core/inc/azure/core/uuid.hpp b/sdk/core/azure-core/inc/azure/core/uuid.hpp index 848e7ec50..6482d9a1e 100644 --- a/sdk/core/azure-core/inc/azure/core/uuid.hpp +++ b/sdk/core/azure-core/inc/azure/core/uuid.hpp @@ -1,6 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +/** + * @file + * @brief Universally unique identifier. + */ + #pragma once #include @@ -10,7 +15,9 @@ #include // for swap and move namespace Azure { namespace Core { - + /** + * @brief Universally unique identifier. + */ class Uuid { private: @@ -24,13 +31,13 @@ namespace Azure { namespace Core { static constexpr uint8_t ReservedFuture = 0x00; private: - Uuid(uint8_t const uuid[UuidSize]) - { - std::memcpy(m_uuid, uuid, UuidSize); - } + Uuid(uint8_t const uuid[UuidSize]) { std::memcpy(m_uuid, uuid, UuidSize); } public: - + /** + * Gets UUID as a string. + * @detail A string is in canonical format (4-2-2-2-6 lowercase hex and dashes only) + */ std::string GetUuidString() { // Guid is 36 characters @@ -61,7 +68,11 @@ namespace Azure { namespace Core { return std::string(s); } - static Uuid CreateUuid() { + /** + * @brief Create a new random UUID. + */ + static Uuid CreateUuid() + { std::random_device rd; std::mt19937 gen(rd()); diff --git a/sdk/core/azure-core/inc/azure/core/version.hpp b/sdk/core/azure-core/inc/azure/core/version.hpp index a5c6bc3b4..1bb227260 100644 --- a/sdk/core/azure-core/inc/azure/core/version.hpp +++ b/sdk/core/azure-core/inc/azure/core/version.hpp @@ -1,6 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // SPDX-License-Identifier: MIT +/** + * @file + * @brief Provides version information. + */ + #pragma once #include @@ -12,12 +17,27 @@ namespace Azure { namespace Core { + /** + * @brief Provides version information. + */ class Version { public: + /// Major numeric identifier. static constexpr int Major = AZURE_CORE_VERSION_MAJOR; + + /// Minor numeric identifier. static constexpr int Minor = AZURE_CORE_VERSION_MINOR; + + /// Patch numeric identifier. static constexpr int Patch = AZURE_CORE_VERSION_PATCH; + + /// Optional pre-release identifier. SDK is in a pre-release state when not empty. static std::string const PreRelease; + + /** + * @brief The version in string format used for telemetry following the `semver.org` standard + * (https://semver.org). + */ static std::string const VersionString(); private: