Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support query compatible trait only for json protocol #3204

Merged
merged 16 commits into from
Nov 26, 2024
4 changes: 2 additions & 2 deletions .github/workflows/clang-format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
run: |
clang-format --version
if [ -s diff_output.patch ]; then
python3 clang-format-diff.py -p1 -style=file:.clang-format < diff_output.patch > formatted_differences.patch 2> error.log || true
python3 clang-format-diff.py -iregex '.*\.(cpp|cc|c\+\+|cxx|c|h|hh|hpp)' -p1 -style=file:.clang-format < diff_output.patch > formatted_differences.patch 2> error.log || true
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

without this just the java file fails clang format in the pipeline, locally , no matter which version

if [ -s error.log ]; then
echo "Errors from clang-format-diff.py:"
cat error.log
Expand All @@ -77,4 +77,4 @@ jobs:
cat formatted_differences.patch
rm formatted_differences.patch
exit 1
fi
fi
43 changes: 24 additions & 19 deletions src/aws-cpp-sdk-core/include/aws/core/client/AWSErrorMarshaller.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,32 +80,37 @@ namespace Aws
{
using AWSErrorMarshaller::Marshall;
public:
/**
* Converts an exceptionName and message into an Error object, if it can be parsed. Otherwise, it returns
* and AWSError with CoreErrors::UNKNOWN as the error type.
*/
AWSError<CoreErrors> Marshall(const Aws::Http::HttpResponse& response) const override;
JsonErrorMarshaller(bool queryCompatibilityMode);
JsonErrorMarshaller() = default;
/**
* Converts an exceptionName and message into an Error object, if it
* can be parsed. Otherwise, it returns and AWSError with
* CoreErrors::UNKNOWN as the error type.
*/
AWSError<CoreErrors> Marshall(const Aws::Http::HttpResponse& response) const override;

AWSError<CoreErrors> BuildAWSError(const std::shared_ptr<Http::HttpResponse>& httpResponse) const override;
AWSError<CoreErrors> BuildAWSError(const std::shared_ptr<Http::HttpResponse>& httpResponse) const override;

protected:
const Aws::Utils::Json::JsonValue& GetJsonPayloadFromError(const AWSError<CoreErrors>&) const;
const Aws::Utils::Json::JsonValue& GetJsonPayloadFromError(const AWSError<CoreErrors>&) const;
bool isQueryCompatibleMode() const;
const bool m_queryCompatibilityMode{false};
};

class AWS_CORE_API XmlErrorMarshaller : public AWSErrorMarshaller
{
using AWSErrorMarshaller::Marshall;
public:
/**
* Converts an exceptionName and message into an Error object, if it can be parsed. Otherwise, it returns
* and AWSError with CoreErrors::UNKNOWN as the error type.
*/
AWSError<CoreErrors> Marshall(const Aws::Http::HttpResponse& response) const override;
class AWS_CORE_API XmlErrorMarshaller : public AWSErrorMarshaller {
using AWSErrorMarshaller::Marshall;

AWSError<CoreErrors> BuildAWSError(const std::shared_ptr<Http::HttpResponse>& httpResponse) const override;
public:
/**
* Converts an exceptionName and message into an Error object, if it can be parsed. Otherwise, it returns
* and AWSError with CoreErrors::UNKNOWN as the error type.
*/
AWSError<CoreErrors> Marshall(const Aws::Http::HttpResponse& response) const override;

protected:
const Aws::Utils::Xml::XmlDocument& GetXmlPayloadFromError(const AWSError<CoreErrors>&) const;
AWSError<CoreErrors> BuildAWSError(const std::shared_ptr<Http::HttpResponse>& httpResponse) const override;

protected:
const Aws::Utils::Xml::XmlDocument& GetXmlPayloadFromError(const AWSError<CoreErrors>&) const;
};

} // namespace Client
Expand Down
1 change: 1 addition & 0 deletions src/aws-cpp-sdk-core/include/aws/core/http/HttpRequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ namespace Aws
extern AWS_CORE_API const char CHUNKED_VALUE[];
extern AWS_CORE_API const char AWS_CHUNKED_VALUE[];
extern AWS_CORE_API const char X_AMZN_ERROR_TYPE[];
extern AWS_CORE_API const char X_AMZN_QUERY_MODE[];

class HttpRequest;
class HttpResponse;
Expand Down
2 changes: 1 addition & 1 deletion src/aws-cpp-sdk-core/source/client/AWSClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1048,4 +1048,4 @@ void AWSClient::AppendRecursionDetectionHeader(std::shared_ptr<Aws::Http::HttpRe
xAmznTraceIdVal = xAmznTraceIdValEncodedStr.str();

ioRequest->SetHeaderValue(Aws::Http::X_AMZN_TRACE_ID_HEADER, xAmznTraceIdVal);
}
}
177 changes: 96 additions & 81 deletions src/aws-cpp-sdk-core/source/client/AWSErrorMarshaller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,95 +40,110 @@ static CoreErrors GuessBodylessErrorType(const Aws::Http::HttpResponseCode respo
}
}

AWSError<CoreErrors> JsonErrorMarshaller::Marshall(const Aws::Http::HttpResponse& httpResponse) const
{
Aws::StringStream memoryStream;
std::copy(std::istreambuf_iterator<char>(httpResponse.GetResponseBody()), std::istreambuf_iterator<char>(), std::ostreambuf_iterator<char>(memoryStream));
Aws::String rawPayloadStr = memoryStream.str();

JsonValue exceptionPayload(rawPayloadStr);
JsonView payloadView(exceptionPayload);
AWSError<CoreErrors> error;
if (exceptionPayload.WasParseSuccessful())
{
AWS_LOGSTREAM_TRACE(AWS_ERROR_MARSHALLER_LOG_TAG, "Error response is " << payloadView.WriteReadable());

Aws::String message(payloadView.ValueExists(MESSAGE_CAMEL_CASE) ? payloadView.GetString(MESSAGE_CAMEL_CASE) :
payloadView.ValueExists(MESSAGE_LOWER_CASE) ? payloadView.GetString(MESSAGE_LOWER_CASE) : "");
JsonErrorMarshaller::JsonErrorMarshaller(bool queryCompatibilityMode)
: AWSErrorMarshaller(), m_queryCompatibilityMode{queryCompatibilityMode} {}

bool JsonErrorMarshaller::isQueryCompatibleMode() const { return m_queryCompatibilityMode; }

AWSError<CoreErrors> JsonErrorMarshaller::Marshall(const Aws::Http::HttpResponse& httpResponse) const {
Aws::StringStream memoryStream;
std::copy(std::istreambuf_iterator<char>(httpResponse.GetResponseBody()), std::istreambuf_iterator<char>(),
std::ostreambuf_iterator<char>(memoryStream));
Aws::String rawPayloadStr = memoryStream.str();

JsonValue exceptionPayload(rawPayloadStr);
JsonView payloadView(exceptionPayload);
AWSError<CoreErrors> error;
if (exceptionPayload.WasParseSuccessful()) {
AWS_LOGSTREAM_TRACE(AWS_ERROR_MARSHALLER_LOG_TAG, "Error response is " << payloadView.WriteReadable());

Aws::String message(payloadView.ValueExists(MESSAGE_CAMEL_CASE) ? payloadView.GetString(MESSAGE_CAMEL_CASE)
: payloadView.ValueExists(MESSAGE_LOWER_CASE) ? payloadView.GetString(MESSAGE_LOWER_CASE)
: "");

if (httpResponse.HasHeader(ERROR_TYPE_HEADER)) {
error = Marshall(httpResponse.GetHeader(ERROR_TYPE_HEADER), message);
} else if (payloadView.ValueExists(TYPE)) {
error = Marshall(payloadView.GetString(TYPE), message);
} else {
error = FindErrorByHttpResponseCode(httpResponse.GetResponseCode());
error.SetMessage(message);
}

if (httpResponse.HasHeader(ERROR_TYPE_HEADER))
{
error = Marshall(httpResponse.GetHeader(ERROR_TYPE_HEADER), message);
}
else if (payloadView.ValueExists(TYPE))
{
error = Marshall(payloadView.GetString(TYPE), message);
if (isQueryCompatibleMode() && !error.GetExceptionName().empty()) {
/*
AWS Query-Compatible mode: This is a special setting that allows
certain AWS services to communicate using a specific "query"
format, which can send customized error codes. Users are divided
into different groups based on how they communicate with the
service: Group #1: Users using the AWS Query format, receiving
custom error codes. Group #2: Users using the regular AWS JSON
format without the trait, receiving standard error codes. Group #3:
Users using the AWS JSON format with the trait, receiving custom
error codes.

The header "x-amzn-query-error" shouldn't be present if it's not
awsQueryCompatible, so added checks for it.
*/

if (httpResponse.HasHeader(QUERY_ERROR_HEADER)) {
auto errorCodeString = httpResponse.GetHeader(QUERY_ERROR_HEADER);
auto locationOfSemicolon = errorCodeString.find_first_of(';');
Aws::String errorCode;

if (locationOfSemicolon != Aws::String::npos) {
errorCode = errorCodeString.substr(0, locationOfSemicolon);
} else {
errorCode = errorCodeString;
}
else
{
error = FindErrorByHttpResponseCode(httpResponse.GetResponseCode());
error.SetMessage(message);
}

if (httpResponse.HasHeader(QUERY_ERROR_HEADER))
{
auto errorCodeString = httpResponse.GetHeader(QUERY_ERROR_HEADER);
auto locationOfSemicolon = errorCodeString.find_first_of(';');
Aws::String errorCode;

if (locationOfSemicolon != Aws::String::npos)
{
errorCode = errorCodeString.substr(0, locationOfSemicolon);
}
else
{
errorCode = errorCodeString;
}

error.SetExceptionName(errorCode);
error.SetExceptionName(errorCode);
}
// check for exception name from payload field 'type'
else if (payloadView.ValueExists(TYPE)) {
// handle missing header and parse code from message
const auto& typeStr = payloadView.GetString(TYPE);
auto locationOfPound = typeStr.find_first_of('#');
if (locationOfPound != Aws::String::npos) {
error.SetExceptionName(typeStr.substr(locationOfPound + 1));
}
}
}
else
{
bool isRetryable = IsRetryableHttpResponseCode(httpResponse.GetResponseCode());
AWS_LOGSTREAM_ERROR(AWS_ERROR_MARSHALLER_LOG_TAG, "Failed to parse error payload: " << httpResponse.GetResponseCode() << ": " << rawPayloadStr);
error = AWSError<CoreErrors>(CoreErrors::UNKNOWN, "", "Failed to parse error payload: " + rawPayloadStr, isRetryable);
}

error.SetRequestId(httpResponse.HasHeader(REQUEST_ID_HEADER) ? httpResponse.GetHeader(REQUEST_ID_HEADER) : "");
error.SetJsonPayload(std::move(exceptionPayload));
return error;
}

AWSError<CoreErrors> JsonErrorMarshaller::BuildAWSError(const std::shared_ptr<Http::HttpResponse>& httpResponse) const
{
AWSError<CoreErrors> error;
if (httpResponse->HasClientError())
{
bool retryable = httpResponse->GetClientErrorType() == CoreErrors::NETWORK_CONNECTION ? true : false;
error = AWSError<CoreErrors>(httpResponse->GetClientErrorType(), "", httpResponse->GetClientErrorMessage(), retryable);
}
else if (!httpResponse->GetResponseBody() || httpResponse->GetResponseBody().tellp() < 1)
{
auto responseCode = httpResponse->GetResponseCode();
auto errorCode = GuessBodylessErrorType(responseCode);
} else {
bool isRetryable = IsRetryableHttpResponseCode(httpResponse.GetResponseCode());
AWS_LOGSTREAM_ERROR(AWS_ERROR_MARSHALLER_LOG_TAG,
"Failed to parse error payload: " << httpResponse.GetResponseCode() << ": " << rawPayloadStr);
error = AWSError<CoreErrors>(CoreErrors::UNKNOWN, "", "Failed to parse error payload: " + rawPayloadStr, isRetryable);
}

Aws::StringStream ss;
ss << "No response body.";
error = AWSError<CoreErrors>(errorCode, "", ss.str(),
IsRetryableHttpResponseCode(responseCode));
}
else
{
assert(httpResponse->GetResponseCode() != HttpResponseCode::OK);
error = Marshall(*httpResponse);
}
error.SetRequestId(httpResponse.HasHeader(REQUEST_ID_HEADER) ? httpResponse.GetHeader(REQUEST_ID_HEADER) : "");
error.SetJsonPayload(std::move(exceptionPayload));
return error;
}

error.SetResponseHeaders(httpResponse->GetHeaders());
error.SetResponseCode(httpResponse->GetResponseCode());
error.SetRemoteHostIpAddress(httpResponse->GetOriginatingRequest().GetResolvedRemoteHost());
AWS_LOGSTREAM_ERROR(AWS_ERROR_MARSHALLER_LOG_TAG, error);
return error;
AWSError<CoreErrors> JsonErrorMarshaller::BuildAWSError(const std::shared_ptr<Http::HttpResponse>& httpResponse) const {
AWSError<CoreErrors> error;
if (httpResponse->HasClientError()) {
bool retryable = httpResponse->GetClientErrorType() == CoreErrors::NETWORK_CONNECTION ? true : false;
error = AWSError<CoreErrors>(httpResponse->GetClientErrorType(), "", httpResponse->GetClientErrorMessage(), retryable);
} else if (!httpResponse->GetResponseBody() || httpResponse->GetResponseBody().tellp() < 1) {
auto responseCode = httpResponse->GetResponseCode();
auto errorCode = GuessBodylessErrorType(responseCode);

Aws::StringStream ss;
ss << "No response body.";
error = AWSError<CoreErrors>(errorCode, "", ss.str(), IsRetryableHttpResponseCode(responseCode));
} else {
assert(httpResponse->GetResponseCode() != HttpResponseCode::OK);
error = Marshall(*httpResponse);
}

error.SetResponseHeaders(httpResponse->GetHeaders());
error.SetResponseCode(httpResponse->GetResponseCode());
error.SetRemoteHostIpAddress(httpResponse->GetOriginatingRequest().GetResolvedRemoteHost());
AWS_LOGSTREAM_ERROR(AWS_ERROR_MARSHALLER_LOG_TAG, error);
return error;
}

const JsonValue& JsonErrorMarshaller::GetJsonPayloadFromError(const AWSError<CoreErrors>& error) const
Expand Down
2 changes: 1 addition & 1 deletion src/aws-cpp-sdk-core/source/http/HttpRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ namespace Aws
const char X_AMZN_TRACE_ID_HEADER[] = "X-Amzn-Trace-Id";
const char ALLOCATION_TAG[] = "HttpRequestConversion";
const char X_AMZN_ERROR_TYPE[] = "x-amzn-errortype";

const char X_AMZN_QUERY_MODE[] = "x-amzn-query-mode";
std::shared_ptr<Aws::Crt::Http::HttpRequest> HttpRequest::ToCrtHttpRequest()
{
auto request = Aws::MakeShared<Aws::Crt::Http::HttpRequest>(ALLOCATION_TAG);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,9 @@ TEST_F(AWSErrorMarshallerTest, TestErrorsWithPrefixParse)
ASSERT_EQ(requestId, error.GetRequestId());
ASSERT_FALSE(error.ShouldRetry());

error = awsErrorMarshaller.Marshall(*BuildHttpResponse(exceptionPrefix + "AccessDeniedException", message, requestId, LowerCaseMessage, "AwsQueryErrorCode"));
JsonErrorMarshaller awsErrorMarshaller2(true);
error = awsErrorMarshaller2.Marshall(
*BuildHttpResponse(exceptionPrefix + "AccessDeniedException", message, requestId, LowerCaseMessage, "AwsQueryErrorCode"));
ASSERT_EQ(CoreErrors::ACCESS_DENIED, error.GetErrorType());
ASSERT_EQ("AwsQueryErrorCode", error.GetExceptionName());
ASSERT_EQ(message, error.GetMessage());
Expand Down Expand Up @@ -741,7 +743,9 @@ TEST_F(AWSErrorMarshallerTest, TestErrorsWithoutPrefixParse)
ASSERT_EQ(requestId, error.GetRequestId());
ASSERT_FALSE(error.ShouldRetry());

error = awsErrorMarshaller.Marshall(*BuildHttpResponse(exceptionPrefix + "AccessDeniedException", message, requestId, LowerCaseMessage, "AwsQueryErrorCode"));
JsonErrorMarshaller awsErrorMarshaller2(true);
error = awsErrorMarshaller2.Marshall(
*BuildHttpResponse(exceptionPrefix + "AccessDeniedException", message, requestId, LowerCaseMessage, "AwsQueryErrorCode"));
ASSERT_EQ(CoreErrors::ACCESS_DENIED, error.GetErrorType());
ASSERT_EQ("AwsQueryErrorCode", error.GetExceptionName());
ASSERT_EQ(message, error.GetMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,14 @@ public ServiceModel convert() {
serviceModel.getMetadata().setHasEndpointDiscoveryTrait(hasEndpointDiscoveryTrait && !endpointOperationName.isEmpty());
serviceModel.getMetadata().setRequireEndpointDiscovery(requireEndpointDiscovery);
serviceModel.getMetadata().setEndpointOperationName(endpointOperationName);
serviceModel.getMetadata().setAwsQueryCompatible(c2jServiceModel.getMetadata().getAwsQueryCompatible() != null);

// add protocol check. only for json, query protocols
if (serviceModel.getMetadata().getProtocol().equals("json")) {
serviceModel.getMetadata().setAwsQueryCompatible(
c2jServiceModel.getMetadata().getAwsQueryCompatible() != null);
} else {
serviceModel.getMetadata().setAwsQueryCompatible(false);
}

if (c2jServiceModel.getEndpointRules() != null) {
ObjectMapper objectMapper = new ObjectMapper();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ namespace ${serviceNamespace}
headers.emplace(Aws::Http::HeaderValuePair(Aws::Http::CONTENT_TYPE_HEADER, ${CppViewHelper.computeRequestContentType($metadata)} ));
#if($metadata.acceptHeader)
headers.emplace(Aws::Http::HeaderValuePair(Aws::Http::ACCEPT_HEADER, "${metadata.acceptHeader}"));
#end
#if ($metadata.awsQueryCompatible)
headers.emplace(Aws::Http::HeaderValuePair(Aws::Http::X_AMZN_QUERY_MODE,"true"));
#end
}
#if($metadata.apiVersion)
Expand Down
Loading
Loading