// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. #include "azure/data/tables/table_client.hpp" #include "azure/data/tables/table_service_client.hpp" #include "private/package_version.hpp" #include "private/policies/service_version_policy.hpp" #include "private/policies/tenant_bearer_token_policy.hpp" #include "private/policies/timeout_policy.hpp" #include "private/serializers.hpp" #include "private/tables_constants.hpp" #include #include using namespace Azure::Data::Tables; using namespace Azure::Data::Tables::_detail::Policies; using namespace Azure::Data::Tables::_detail::Xml; using namespace Azure::Data::Tables::_detail; TableServiceClient::TableServiceClient( const std::string& serviceUrl, const TableClientOptions& options) { m_url = Azure::Core::Url(serviceUrl); std::vector> perRetryPolicies; std::vector> perOperationPolicies; perRetryPolicies.emplace_back(std::make_unique()); perOperationPolicies.emplace_back(std::make_unique(options.ApiVersion)); m_pipeline = std::make_shared( options, _detail::TablesServicePackageName, PackageVersion::ToString(), std::move(perRetryPolicies), std::move(perOperationPolicies)); } TableServiceClient::TableServiceClient( const std::string& serviceUrl, std::shared_ptr credential, const TableClientOptions& options) { m_tokenCredential = credential; TableClientOptions newOptions = options; m_url = Azure::Core::Url(serviceUrl); std::vector> perRetryPolicies; std::vector> perOperationPolicies; perRetryPolicies.emplace_back(std::make_unique()); perOperationPolicies.emplace_back(std::make_unique(options.ApiVersion)); perRetryPolicies.emplace_back(std::make_unique()); { Azure::Core::Credentials::TokenRequestContext tokenContext; tokenContext.Scopes.emplace_back(m_url.GetAbsoluteUrl() + "/.default"); perRetryPolicies.emplace_back(std::make_unique( credential, tokenContext, newOptions.EnableTenantDiscovery)); } perOperationPolicies.emplace_back(std::make_unique(newOptions.ApiVersion)); m_pipeline = std::make_shared( newOptions, _detail::TablesServicePackageName, PackageVersion::ToString(), std::move(perRetryPolicies), std::move(perOperationPolicies)); } TableClient TableServiceClient::GetTableClient( const std::string& tableName, TableClientOptions const& options) const { if (m_tokenCredential != nullptr) { return TableClient(m_url.GetAbsoluteUrl(), tableName, m_tokenCredential, options); } if (!m_url.GetAbsoluteUrl().empty()) { return TableClient(m_url.GetAbsoluteUrl(), tableName, options); } throw std::runtime_error("TableServiceClient is not properly initialized."); } Azure::Response TableServiceClient::PreflightCheck( Models::PreflightCheckOptions const& options, Core::Context const& context) const { auto url = m_url; url.AppendPath(Azure::Core::Url::Encode(options.TableName)); Core::Http::Request request(Core::Http::HttpMethod::Options, url); request.SetHeader(OriginHeader, options.Origin); request.SetHeader(AccessControlRequestMethodHeader, Core::Http::HttpMethod::Options.ToString()); auto rawResponse = m_pipeline->Send(request, context); auto const httpStatusCode = rawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::Ok) { throw Core::RequestFailedException(rawResponse); } Models::PreflightCheckResult response; return Response(std::move(response), std::move(rawResponse)); } Azure::Response TableServiceClient::SetServiceProperties( Models::SetServicePropertiesOptions const& options, Core::Context const& context) const { std::string xmlBody = Serializers::SetServiceProperties(options); auto url = m_url; url.AppendQueryParameter(ResourceTypeHeader, ResourceTypeService); url.AppendQueryParameter(CompHeader, ComponentProperties); Core::IO::MemoryBodyStream requestBody( reinterpret_cast(xmlBody.data()), xmlBody.length()); Core::Http::Request request(Core::Http::HttpMethod::Put, url, &requestBody); request.SetHeader(ContentTypeHeader, ContentTypeXml); request.SetHeader(ContentLengthHeader, std::to_string(requestBody.Length())); auto rawResponse = m_pipeline->Send(request, context); auto const httpStatusCode = rawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::Accepted) { throw Core::RequestFailedException(rawResponse); } Models::SetServicePropertiesResult response; return Response(std::move(response), std::move(rawResponse)); } Azure::Response TableServiceClient::GetServiceProperties( Core::Context const& context) const { auto url = m_url; url.AppendQueryParameter(ResourceTypeHeader, ResourceTypeService); url.AppendQueryParameter(CompHeader, ComponentProperties); Core::Http::Request request(Core::Http::HttpMethod::Get, url); auto pRawResponse = m_pipeline->Send(request, context); auto const httpStatusCode = pRawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::Ok) { throw Core::RequestFailedException(pRawResponse); } Models::TableServiceProperties response = Serializers::ServicePropertiesFromXml(pRawResponse->GetBody()); return Response(std::move(response), std::move(pRawResponse)); } Azure::Response TableServiceClient::GetStatistics( const Core::Context& context) const { auto url = m_url; std::string host = url.GetHost(); std::string accountName = host.substr(0, host.find('.')); accountName += "-secondary"; url.SetHost(accountName + "." + host.substr(host.find('.') + 1)); url.AppendQueryParameter(ResourceTypeHeader, ResourceTypeService); url.AppendQueryParameter(CompHeader, "stats"); Core::Http::Request request(Core::Http::HttpMethod::Get, url); auto pRawResponse = m_pipeline->Send(request, context); auto httpStatusCode = pRawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::Ok) { throw Core::RequestFailedException(pRawResponse); } Models::ServiceStatistics response; { const auto& responseBody = pRawResponse->GetBody(); Xml::XmlReader reader(reinterpret_cast(responseBody.data()), responseBody.size()); enum class XmlTagEnum { kUnknown, kStorageServiceStats, kGeoReplication, kStatus, kLastSyncTime, }; const std::unordered_map XmlTagEnumMap{ {"StorageServiceStats", XmlTagEnum::kStorageServiceStats}, {"GeoReplication", XmlTagEnum::kGeoReplication}, {"Status", XmlTagEnum::kStatus}, {"LastSyncTime", XmlTagEnum::kLastSyncTime}, }; std::vector xmlPath; while (true) { auto node = reader.Read(); if (node.Type == XmlNodeType::End) { break; } else if (node.Type == XmlNodeType::StartTag) { auto ite = XmlTagEnumMap.find(node.Name); xmlPath.push_back(ite == XmlTagEnumMap.end() ? XmlTagEnum::kUnknown : ite->second); } else if (node.Type == XmlNodeType::Text) { if (xmlPath.size() == 3 && xmlPath[0] == XmlTagEnum::kStorageServiceStats && xmlPath[1] == XmlTagEnum::kGeoReplication && xmlPath[2] == XmlTagEnum::kStatus) { response.GeoReplication.Status = Models::GeoReplicationStatus(node.Value); } else if ( xmlPath.size() == 3 && xmlPath[0] == XmlTagEnum::kStorageServiceStats && xmlPath[1] == XmlTagEnum::kGeoReplication && xmlPath[2] == XmlTagEnum::kLastSyncTime) { response.GeoReplication.LastSyncedOn = DateTime::Parse(node.Value, Azure::DateTime::DateFormat::Rfc1123); } } else if (node.Type == XmlNodeType::Attribute) { } else if (node.Type == XmlNodeType::EndTag) { xmlPath.pop_back(); } } } return Response(std::move(response), std::move(pRawResponse)); } TableClient::TableClient( std::string const& serviceUrl, std::string const& tableName, const TableClientOptions& options) : m_url(serviceUrl), m_tableName(tableName) { std::vector> perRetryPolicies; std::vector> perOperationPolicies; perRetryPolicies.emplace_back(std::make_unique()); perOperationPolicies.emplace_back(std::make_unique(options.ApiVersion)); m_pipeline = std::make_shared( options, _detail::TablesServicePackageName, PackageVersion::ToString(), std::move(perRetryPolicies), std::move(perOperationPolicies)); } TableClient::TableClient( const std::string& serviceUrl, const std::string& tableName, std::shared_ptr credential, const TableClientOptions& options) : TableClient(serviceUrl, tableName, options) { m_tableName = tableName; TableClientOptions newOptions = options; m_url = Azure::Core::Url(serviceUrl); std::vector> perRetryPolicies; std::vector> perOperationPolicies; perRetryPolicies.emplace_back(std::make_unique()); perOperationPolicies.emplace_back(std::make_unique(options.ApiVersion)); perRetryPolicies.emplace_back(std::make_unique()); { Azure::Core::Credentials::TokenRequestContext tokenContext; tokenContext.Scopes.emplace_back(m_url.GetAbsoluteUrl() + "/.default"); perRetryPolicies.emplace_back(std::make_unique( credential, tokenContext, newOptions.EnableTenantDiscovery)); } perOperationPolicies.emplace_back(std::make_unique(newOptions.ApiVersion)); m_pipeline = std::make_shared( newOptions, _detail::TablesServicePackageName, PackageVersion::ToString(), std::move(perRetryPolicies), std::move(perOperationPolicies)); } Azure::Response TableServiceClient::CreateTable( std::string const& tableName, Core::Context const& context) const { auto url = m_url; url.AppendPath("Tables"); std::string jsonBody = Serializers::Create(tableName); Core::IO::MemoryBodyStream requestBody( reinterpret_cast(jsonBody.data()), jsonBody.length()); Core::Http::Request request(Core::Http::HttpMethod::Post, url, &requestBody); request.SetHeader(ContentTypeHeader, ContentTypeJson); request.SetHeader(ContentLengthHeader, std::to_string(requestBody.Length())); request.SetHeader(AcceptHeader, AcceptFullMeta); auto rawResponse = m_pipeline->Send(request, context); auto const httpStatusCode = rawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::Created) { throw Core::RequestFailedException(rawResponse); } Models::Table response{}; { auto const& responseBody = rawResponse->GetBody(); if (responseBody.size() > 0) { auto const jsonRoot = Core::Json::_internal::json::parse(responseBody.begin(), responseBody.end()); response.TableName = jsonRoot[TableName].get(); response.EditLink = jsonRoot[ODataEditLink].get(); response.Id = jsonRoot[ODataId].get(); response.Metadata = jsonRoot[ODataMeta].get(); response.Type = jsonRoot[ODataType].get(); } } return Response(std::move(response), std::move(rawResponse)); } void Models::QueryTablesPagedResponse::OnNextPage(const Azure::Core::Context& context) { m_operationOptions.ContinuationToken = NextPageToken; *this = m_tableServiceClient->QueryTables(m_operationOptions, context); } Models::QueryTablesPagedResponse TableServiceClient::QueryTables( Models::QueryTablesOptions const& options, Azure::Core::Context const& context) const { auto url = m_url; url.AppendPath("Tables"); Core::Http::Request request(Core::Http::HttpMethod::Get, url); request.SetHeader(AcceptHeader, AcceptFullMeta); if (options.Prefix.HasValue()) { request.GetUrl().AppendQueryParameter(IfMatch, options.Prefix.Value()); } if (options.ContinuationToken.HasValue()) { request.GetUrl().AppendQueryParameter("NextTableName", options.ContinuationToken.Value()); } auto rawResponse = m_pipeline->Send(request, context); auto const httpStatusCode = rawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::Ok) { throw Core::RequestFailedException(rawResponse); } Models::QueryTablesPagedResponse response{std::make_shared(*this)}; { auto const& responseBody = rawResponse->GetBody(); if (responseBody.size() > 0) { auto const jsonRoot = Core::Json::_internal::json::parse(responseBody.begin(), responseBody.end()); std::string metadataLink = jsonRoot[ODataMeta].get(); for (auto value : jsonRoot[Value]) { Models::Table table; table.TableName = value[TableName].get(); table.EditLink = value[ODataEditLink].get(); table.Id = value[ODataId].get(); table.Type = value[ODataType].get(); table.Metadata = metadataLink; response.Tables.emplace_back(std::move(table)); } } response.ServiceEndpoint = url.GetAbsoluteUrl(); response.Prefix = options.Prefix; response.m_operationOptions = options; response.CurrentPageToken = options.ContinuationToken.ValueOr(std::string()); response.RawResponse = std::move(response.RawResponse); auto headers = rawResponse->GetHeaders(); if (headers.find("x-ms-continuation-NextTableName") != headers.end()) { response.NextPageToken = headers.at("x-ms-continuation-NextTableName"); } } return response; } Azure::Response TableServiceClient::DeleteTable( std::string const& tableName, Core::Context const& context) const { auto url = m_url; url.AppendPath("Tables('" + Azure::Core::Url::Encode(tableName) + ClosingFragment); Core::Http::Request request(Core::Http::HttpMethod::Delete, url); request.SetHeader(ContentTypeHeader, ContentTypeJson); request.SetHeader(AcceptHeader, AcceptFullMeta); auto rawResponse = m_pipeline->Send(request, context); auto const httpStatusCode = rawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::NoContent) { throw Core::RequestFailedException(rawResponse); } Models::DeleteTableResult response{}; return Response(std::move(response), std::move(rawResponse)); } Azure::Response TableClient::AddEntity( Models::TableEntity const& tableEntity, Core::Context const& context) const { auto url = m_url; url.AppendPath(Azure::Core::Url::Encode(m_tableName)); std::string jsonBody = Serializers::CreateEntity(tableEntity); Core::IO::MemoryBodyStream requestBody( reinterpret_cast(jsonBody.data()), jsonBody.length()); Core::Http::Request request(Core::Http::HttpMethod::Post, url, &requestBody); request.SetHeader(ContentTypeHeader, ContentTypeJson); request.SetHeader(ContentLengthHeader, std::to_string(requestBody.Length())); request.SetHeader(AcceptHeader, AcceptFullMeta); request.SetHeader(PreferHeader, PreferNoContent); auto rawResponse = m_pipeline->Send(request, context); auto const httpStatusCode = rawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::NoContent) { throw Core::RequestFailedException(rawResponse); } Models::AddEntityResult response{}; response.ETag = rawResponse->GetHeaders().at("ETag"); return Response(std::move(response), std::move(rawResponse)); } Azure::Response TableClient::UpdateEntityImpl( Models::TableEntity const& tableEntity, bool isUpsert, Core::Context const& context) const { auto url = m_url; url.AppendPath(Azure::Core::Url::Encode( m_tableName + PartitionKeyFragment + tableEntity.GetPartitionKey().Value + RowKeyFragment + tableEntity.GetRowKey().Value + ClosingFragment)); std::string jsonBody = Serializers::UpdateEntity(tableEntity); Core::IO::MemoryBodyStream requestBody( reinterpret_cast(jsonBody.data()), jsonBody.length()); Core::Http::Request request(Core::Http::HttpMethod::Put, url, &requestBody); request.SetHeader(ContentTypeHeader, ContentTypeJson); request.SetHeader(ContentLengthHeader, std::to_string(requestBody.Length())); request.SetHeader(AcceptHeader, AcceptFullMeta); request.SetHeader(PreferHeader, PreferNoContent); if (!isUpsert && !tableEntity.GetETag().Value.empty()) { request.SetHeader(IfMatch, tableEntity.GetETag().Value); } auto rawResponse = m_pipeline->Send(request, context); auto const httpStatusCode = rawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::NoContent) { throw Core::RequestFailedException(rawResponse); } Models::UpdateEntityResult response{}; response.ETag = rawResponse->GetHeaders().at("ETag"); return Response(std::move(response), std::move(rawResponse)); } Azure::Response TableClient::MergeEntityImpl( Models::TableEntity const& tableEntity, bool isUpsert, Core::Context const& context) const { auto url = m_url; url.AppendPath(Azure::Core::Url::Encode( m_tableName + PartitionKeyFragment + tableEntity.GetPartitionKey().Value + RowKeyFragment + tableEntity.GetRowKey().Value + ClosingFragment)); std::string jsonBody = Serializers::MergeEntity(tableEntity); Core::IO::MemoryBodyStream requestBody( reinterpret_cast(jsonBody.data()), jsonBody.length()); Core::Http::Request request(Core::Http::HttpMethod::Patch, url, &requestBody); request.SetHeader(ContentTypeHeader, ContentTypeJson); request.SetHeader(ContentLengthHeader, std::to_string(requestBody.Length())); request.SetHeader(AcceptHeader, AcceptFullMeta); request.SetHeader(PreferHeader, PreferNoContent); if (!isUpsert && !tableEntity.GetETag().Value.empty()) { request.SetHeader(IfMatch, tableEntity.GetETag().Value); } auto rawResponse = m_pipeline->Send(request, context); auto const httpStatusCode = rawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::NoContent) { throw Core::RequestFailedException(rawResponse); } Models::MergeEntityResult response{}; response.ETag = rawResponse->GetHeaders().at("ETag"); return Response(std::move(response), std::move(rawResponse)); } Azure::Response TableClient::DeleteEntity( Models::TableEntity const& tableEntity, Core::Context const& context) const { auto url = m_url; url.AppendPath(Azure::Core::Url::Encode( m_tableName + PartitionKeyFragment + tableEntity.GetPartitionKey().Value + RowKeyFragment + tableEntity.GetRowKey().Value + ClosingFragment)); Core::Http::Request request(Core::Http::HttpMethod::Delete, url); if (!tableEntity.GetETag().Value.empty()) { request.SetHeader(IfMatch, tableEntity.GetETag().Value); } else { request.SetHeader(IfMatch, "*"); } request.SetHeader(AcceptHeader, AcceptFullMeta); auto rawResponse = m_pipeline->Send(request, context); auto const httpStatusCode = rawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::NoContent) { throw Core::RequestFailedException(rawResponse); } Models::DeleteEntityResult response{}; return Response(std::move(response), std::move(rawResponse)); } Azure::Response TableClient::UpdateOrInsertEntity( Models::TableEntity const& tableEntity, Core::Context const& context) const { return UpdateEntityImpl(tableEntity, true, context); } Azure::Response TableClient::MergeOrInsertEntity( Models::TableEntity const& tableEntity, Core::Context const& context) const { return MergeEntityImpl(tableEntity, true, context); } Azure::Response TableClient::UpdateEntity( Models::TableEntity const& tableEntity, Core::Context const& context) const { return UpdateEntityImpl(tableEntity, false, context); } Azure::Response TableClient::MergeEntity( Models::TableEntity const& tableEntity, Core::Context const& context) const { return MergeEntityImpl(tableEntity, false, context); } void Models::QueryEntitiesPagedResponse::OnNextPage(const Azure::Core::Context& context) { m_operationOptions.NextPartitionKey = NextPartitionKey; m_operationOptions.NextRowKey = NextRowKey; *this = m_tableClient->QueryEntities(m_operationOptions, context); } Azure::Response TableClient::GetEntity( const std::string& partitionKey, const std::string& rowKey, Core::Context const& context) const { auto url = m_url; url.AppendPath(Azure::Core::Url::Encode( m_tableName + PartitionKeyFragment + partitionKey + RowKeyFragment + rowKey + ClosingFragment)); Core::Http::Request request(Core::Http::HttpMethod::Get, url); request.SetHeader(AcceptHeader, AcceptFullMeta); auto rawResponse = m_pipeline->Send(request, context); auto const httpStatusCode = rawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::Ok) { throw Core::RequestFailedException(rawResponse); } Models::TableEntity response{}; { const auto& responseBody = rawResponse->GetBody(); auto const jsonRoot = Azure::Core::Json::_internal::json::parse(responseBody.begin(), responseBody.end()); response = Serializers::DeserializeEntity(jsonRoot); } return Response(std::move(response), std::move(rawResponse)); } Models::QueryEntitiesPagedResponse TableClient::QueryEntities( Models::QueryEntitiesOptions const& options, Core::Context const& context) const { auto url = m_url; if (!options.NextPartitionKey.empty() && !options.NextRowKey.empty()) { url.AppendPath(Azure::Core::Url::Encode(m_tableName)); url.AppendQueryParameter( "NextPartitionKey", Azure::Core::Url::Encode(options.NextPartitionKey)); url.AppendQueryParameter("NextRowKey", Azure::Core::Url::Encode(options.NextRowKey)); } else { std::string appendPath = Azure::Core::Url::Encode(m_tableName) + "("; if (!options.PartitionKey.empty()) { appendPath += "PartitionKey='" + Azure::Core::Url::Encode(options.PartitionKey) + "'"; } if (!options.RowKey.empty()) { appendPath += ",RowKey='" + Azure::Core::Url::Encode(options.RowKey) + "'"; } appendPath += ")"; url.AppendPath(appendPath); } if (options.Filter.HasValue()) { url.AppendQueryParameter("$filter", Azure::Core::Url::Encode(options.Filter.Value())); } if (!options.SelectColumns.empty()) { url.AppendQueryParameter("$select", Azure::Core::Url::Encode(options.SelectColumns)); } Core::Http::Request request(Core::Http::HttpMethod::Get, url); request.SetHeader(AcceptHeader, AcceptFullMeta); auto rawResponse = m_pipeline->Send(request, context); auto const httpStatusCode = rawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::Ok) { throw Core::RequestFailedException(rawResponse); } Models::QueryEntitiesPagedResponse response(std::make_shared(*this)); { const auto& responseBody = rawResponse->GetBody(); auto headers = rawResponse->GetHeaders(); if (headers.find("x-ms-continuation-NextPartitionKey") != headers.end()) { response.NextPartitionKey = headers.at("x-ms-continuation-NextPartitionKey"); } if (headers.find("x-ms-continuation-NextRowKey") != headers.end()) { response.NextRowKey = headers.at("x-ms-continuation-NextRowKey"); } if (!response.NextPartitionKey.empty() || !response.NextRowKey.empty()) { response.NextPageToken = "true"; } response.TableEntities.clear(); auto const jsonRoot = Core::Json::_internal::json::parse(responseBody.begin(), responseBody.end()); if (!jsonRoot.contains(Value)) { response.TableEntities.emplace_back(Serializers::DeserializeEntity(jsonRoot)); } else { for (auto value : jsonRoot[Value]) { response.TableEntities.emplace_back(Serializers::DeserializeEntity(value)); } } } return response; } Azure::Response TableClient::SubmitTransaction( std::vector const& steps, Core::Context const& context) const { auto url = m_url; url.AppendPath("$batch"); std::string const batchId = "batch_" + Azure::Core::Uuid::CreateUuid().ToString(); std::string const changesetId = "changeset_" + Azure::Core::Uuid::CreateUuid().ToString(); std::string const body = PreparePayload(batchId, changesetId, steps); Core::IO::MemoryBodyStream requestBody( reinterpret_cast(body.data()), body.length()); Core::Http::Request request(Core::Http::HttpMethod::Post, url, &requestBody); request.SetHeader(ContentTypeHeader, "multipart/mixed; boundary=" + batchId); request.SetHeader(AcceptHeader, AcceptFullMeta); request.SetHeader(ContentLengthHeader, std::to_string(requestBody.Length())); request.SetHeader("Connection", "Keep-Alive"); request.SetHeader("DataServiceVersion", "3.0"); request.SetHeader("Accept-Charset", "UTF-8"); auto rawResponse = m_pipeline->Send(request, context); auto const httpStatusCode = rawResponse->GetStatusCode(); if (httpStatusCode != Core::Http::HttpStatusCode::Accepted) { throw Core::RequestFailedException(rawResponse); } Models::SubmitTransactionResult response{}; { const auto& responseBody = rawResponse->GetBody(); std::string responseString = std::string(responseBody.begin(), responseBody.end()); std::stringstream ss(responseString); std::string line; std::getline(ss, line, '\n'); Models::TransactionError error; while (line.size() != 0) { std::getline(ss, line, '\n'); if (line.find("HTTP") != std::string::npos) { std::string status = line.substr(line.find(" ") + 1, 3); response.StatusCode = status; } if (line.find(ODataError) != std::string::npos) { auto const jsonRoot = Core::Json::_internal::json::parse(line.begin(), line.end()); if (jsonRoot[ODataError].contains("code")) { error.Code = jsonRoot[ODataError]["code"].get(); } if (jsonRoot[ODataError].contains("message") && jsonRoot[ODataError]["message"].contains(Value)) { error.Message = jsonRoot[ODataError]["message"][Value].get(); } } } if (error.Message.size() != 0) { response.Error = error; } } return Response(std::move(response), std::move(rawResponse)); } std::string TableClient::PreparePayload( std::string const& batchId, std::string const& changesetId, std::vector const& steps) const { std::string accumulator = "--" + batchId + "\nContent-Type: multipart/mixed; boundary=" + changesetId + "\n\n"; for (auto step : steps) { switch (step.Action) { case Models::TransactionActionType::Add: accumulator += PrepAddEntity(changesetId, step.Entity); break; case Models::TransactionActionType::Delete: accumulator += PrepDeleteEntity(changesetId, step.Entity); break; case Models::TransactionActionType::InsertMerge: case Models::TransactionActionType::UpdateMerge: accumulator += PrepMergeEntity(changesetId, step.Entity); break; case Models::TransactionActionType::InsertReplace: accumulator += PrepInsertEntity(changesetId, step.Entity); break; case Models::TransactionActionType::UpdateReplace: accumulator += PrepUpdateEntity(changesetId, step.Entity); break; } } accumulator += "\n\n--" + changesetId + "--\n"; accumulator += "--" + batchId + "\n"; return accumulator; } std::string TableClient::PrepAddEntity(std::string const& changesetId, Models::TableEntity entity) const { std::string returnValue = "--" + changesetId + "\n"; returnValue += "Content-Type: application/http\n"; returnValue += "Content-Transfer-Encoding: binary\n\n"; auto url = m_url; url.AppendPath(m_tableName); returnValue += "POST " + url.GetAbsoluteUrl() + " HTTP/1.1\n"; returnValue += "Content-Type: application/json\n"; returnValue += "Accept: application/json;odata=minimalmetadata\n"; returnValue += "Prefer: return-no-content\n"; returnValue += "DataServiceVersion: 3.0;\n\n"; returnValue += Serializers::CreateEntity(entity); return returnValue; } std::string TableClient::PrepDeleteEntity( std::string const& changesetId, Models::TableEntity entity) const { std::string returnValue = "--" + changesetId + "\n"; returnValue += "Content-Type: application/http\n"; returnValue += "Content-Transfer-Encoding: binary\n\n"; auto url = m_url; url.AppendPath( m_tableName + PartitionKeyFragment + entity.GetPartitionKey().Value + RowKeyFragment + entity.GetRowKey().Value + ClosingFragment); returnValue += "DELETE " + url.GetAbsoluteUrl() + " HTTP/1.1\n"; returnValue += "Accept: application/json;odata=minimalmetadata\n"; returnValue += "Prefer: return-no-content\n"; returnValue += "DataServiceVersion: 3.0;\n"; if (!entity.GetETag().Value.empty()) { returnValue += "If-Match: " + entity.GetETag().Value; } else { returnValue += "If-Match: *"; } returnValue += "\n"; return returnValue; } std::string TableClient::PrepMergeEntity(std::string const& changesetId, Models::TableEntity entity) const { std::string returnValue = "--" + changesetId + "\n"; returnValue += "Content-Type: application/http\n"; returnValue += "Content-Transfer-Encoding: binary\n\n"; auto url = m_url; url.AppendPath( m_tableName + PartitionKeyFragment + entity.GetPartitionKey().Value + RowKeyFragment + entity.GetRowKey().Value + ClosingFragment); returnValue += "MERGE " + url.GetAbsoluteUrl() + " HTTP/1.1\n"; returnValue += "Content-Type: application/json\n"; returnValue += "Accept: application/json;odata=minimalmetadata\n"; returnValue += "DataServiceVersion: 3.0;\n\n"; returnValue += Serializers::MergeEntity(entity); return returnValue; } std::string TableClient::PrepUpdateEntity( std::string const& changesetId, Models::TableEntity entity) const { std::string returnValue = "--" + changesetId + "\n"; returnValue += "Content-Type: application/http\n"; returnValue += "Content-Transfer-Encoding: binary\n\n"; auto url = m_url; url.AppendPath( m_tableName + PartitionKeyFragment + entity.GetPartitionKey().Value + RowKeyFragment + entity.GetRowKey().Value + ClosingFragment); returnValue += "PUT " + url.GetAbsoluteUrl() + " HTTP/1.1\n"; returnValue += "Content-Type: application/json\n"; returnValue += "Accept: application/json;odata=minimalmetadata\n"; returnValue += "Prefer: return-no-content\n"; returnValue += "DataServiceVersion: 3.0;\n"; if (!entity.GetETag().Value.empty()) { returnValue += "If-Match: " + entity.GetETag().Value; } else { returnValue += "If-Match: *"; } returnValue += "\n\n"; returnValue += Serializers::UpdateEntity(entity); return returnValue; } std::string TableClient::PrepInsertEntity( std::string const& changesetId, Models::TableEntity entity) const { std::string payload = Serializers::UpdateEntity(entity); std::string returnValue = "--" + changesetId + "\n"; returnValue += "Content-Type: application/http\n"; returnValue += "Content-Transfer-Encoding: binary\n\n"; auto url = m_url; url.AppendPath( m_tableName + PartitionKeyFragment + entity.GetPartitionKey().Value + RowKeyFragment + entity.GetRowKey().Value + ClosingFragment); returnValue += "PATCH " + url.GetAbsoluteUrl() + " HTTP/1.1\n"; returnValue += "Content-Type: application/json\n"; returnValue += "Content-Length: " + std::to_string(payload.length()); returnValue += "Accept: application/json;odata=minimalmetadata\n"; returnValue += "Prefer: return-no-content\n"; returnValue += "DataServiceVersion: 3.0;\n"; if (!entity.GetETag().Value.empty()) { returnValue += "If-Match: " + entity.GetETag().Value; } else { returnValue += "If-Match: *"; } returnValue += "\n\n"; return returnValue; }