diff --git a/include/aws/lambda-runtime/runtime.h b/include/aws/lambda-runtime/runtime.h index 0203245..ea32c9a 100644 --- a/include/aws/lambda-runtime/runtime.h +++ b/include/aws/lambda-runtime/runtime.h @@ -77,28 +77,60 @@ struct invocation_request { inline std::chrono::milliseconds get_time_remaining() const; }; -class invocation_response { -private: +class runtime_response { +protected: /** - * The output of the function which is sent to the lambda caller. + * The response payload from the runtime. */ std::string m_payload; /** * The MIME type of the payload. - * This is always set to 'application/json' in unsuccessful invocations. */ std::string m_content_type; /** - * Flag to distinguish if the contents are for successful or unsuccessful invocations. + * The serialized XRay response header. */ - bool m_success; + std::string m_xray_response; /** - * The serialized XRay response header. + * Instantiate an empty response. */ - std::string m_xray_response; + runtime_response() = default; + +public: + /* Create a runtime response with the given payload, content type and xray response. This can be used for + * constructing an initialization error response. For invocation success and failure response, see + * invocation_response. + */ + runtime_response(std::string const& payload, std::string const& content_type, std::string const& xray_response) + : m_payload(payload), m_content_type(content_type), m_xray_response(xray_response) + { + } + + /** + * Get the payload string. The string is assumed to be UTF-8 encoded. + */ + std::string const& get_payload() const { return m_payload; } + + /** + * Get the MIME type of the payload. + */ + std::string const& get_content_type() const { return m_content_type; } + + /** + * Get the XRay response string. The string is assumed to be UTF-8 encoded. + */ + std::string const& get_xray_response() const { return m_xray_response; } +}; + +class invocation_response : public runtime_response { +private: + /** + * Flag to distinguish if the contents are for successful or unsuccessful invocations. + */ + bool m_success; /** * Instantiate an empty response. Used by the static functions 'success' and 'failure' to create a populated @@ -113,7 +145,7 @@ class invocation_response { // constructor should be used instead. // Note: adding an overload to invocation_response::failure is not feasible since the parameter types are the same. invocation_response(std::string const& payload, std::string const& content_type, bool success) - : m_payload(payload), m_content_type(content_type), m_success(success) + : runtime_response(payload, content_type, ""), m_success(success) { } @@ -122,7 +154,7 @@ class invocation_response { std::string const& content_type, bool success, std::string const& xray_response) - : m_payload(payload), m_content_type(content_type), m_success(success), m_xray_response(xray_response) + : runtime_response(payload, content_type, xray_response), m_success(success) { } @@ -142,25 +174,10 @@ class invocation_response { std::string const& error_type, std::string const& xray_response); - /** - * Get the MIME type of the payload. - */ - std::string const& get_content_type() const { return m_content_type; } - - /** - * Get the payload string. The string is assumed to be UTF-8 encoded. - */ - std::string const& get_payload() const { return m_payload; } - /** * Returns true if the payload and content-type are set. Returns false if the error message and error types are set. */ bool is_success() const { return m_success; } - - /** - * Get the XRay response string. The string isassumed to be UTF-8 encoded. - */ - std::string const& get_xray_response() const { return m_xray_response; } }; struct no_result {}; @@ -189,13 +206,19 @@ class runtime { */ post_outcome post_failure(std::string const& request_id, invocation_response const& handler_response); + /** + * Tells lambda that the runtime has failed during initialization. + */ + post_outcome post_init_error(runtime_response const& init_error_response); + private: void set_curl_next_options(); static void set_curl_post_result_options(); post_outcome do_post( std::string const& url, - std::string const& request_id, - invocation_response const& handler_response); + std::string const& content_type, + std::string const& payload, + std::string const& xray_response); std::string const m_user_agent_header; std::array const m_endpoints; }; diff --git a/src/runtime.cpp b/src/runtime.cpp index 067de96..1efab45 100644 --- a/src/runtime.cpp +++ b/src/runtime.cpp @@ -338,40 +338,50 @@ runtime::next_outcome runtime::get_next() runtime::post_outcome runtime::post_success(std::string const& request_id, invocation_response const& handler_response) { std::string const url = m_endpoints[Endpoints::RESULT] + request_id + "/response"; - return do_post(url, request_id, handler_response); + return do_post( + url, handler_response.get_content_type(), handler_response.get_payload(), handler_response.get_xray_response()); } runtime::post_outcome runtime::post_failure(std::string const& request_id, invocation_response const& handler_response) { std::string const url = m_endpoints[Endpoints::RESULT] + request_id + "/error"; - return do_post(url, request_id, handler_response); + return do_post( + url, handler_response.get_content_type(), handler_response.get_payload(), handler_response.get_xray_response()); +} + +runtime::post_outcome runtime::post_init_error(runtime_response const& init_error_response) +{ + std::string const url = m_endpoints[Endpoints::INIT]; + return do_post( + url, + init_error_response.get_content_type(), + init_error_response.get_payload(), + init_error_response.get_xray_response()); } runtime::post_outcome runtime::do_post( std::string const& url, - std::string const& request_id, - invocation_response const& handler_response) + std::string const& content_type, + std::string const& payload, + std::string const& xray_response) { set_curl_post_result_options(); curl_easy_setopt(lambda_runtime::m_curl_handle, CURLOPT_URL, url.c_str()); logging::log_info(LOG_TAG, "Making request to %s", url.c_str()); curl_slist* headers = nullptr; - if (handler_response.get_content_type().empty()) { + if (content_type.empty()) { headers = curl_slist_append(headers, "content-type: text/html"); } else { - headers = curl_slist_append(headers, ("content-type: " + handler_response.get_content_type()).c_str()); + headers = curl_slist_append(headers, ("content-type: " + content_type).c_str()); } - if (!handler_response.get_xray_response().empty()) { - headers = curl_slist_append( - headers, ("lambda-runtime-function-xray-error-cause: " + handler_response.get_xray_response()).c_str()); - } + headers = curl_slist_append(headers, ("lambda-runtime-function-xray-error-cause: " + xray_response).c_str()); headers = curl_slist_append(headers, "Expect:"); headers = curl_slist_append(headers, "transfer-encoding:"); headers = curl_slist_append(headers, m_user_agent_header.c_str()); - auto const& payload = handler_response.get_payload(); + logging::log_debug( LOG_TAG, "calculating content length... %s", ("content-length: " + std::to_string(payload.length())).c_str()); headers = curl_slist_append(headers, ("content-length: " + std::to_string(payload.length())).c_str()); @@ -388,10 +398,10 @@ runtime::post_outcome runtime::do_post( if (curl_code != CURLE_OK) { logging::log_debug( LOG_TAG, - "CURL returned error code %d - %s, for invocation %s", + "CURL returned error code %d - %s, when calling %s", curl_code, curl_easy_strerror(curl_code), - request_id.c_str()); + url.c_str()); return aws::http::response_code::REQUEST_NOT_MADE; } @@ -400,7 +410,10 @@ runtime::post_outcome runtime::do_post( if (!is_success(aws::http::response_code(http_response_code))) { logging::log_error( - LOG_TAG, "Failed to post handler success response. Http response code: %ld.", http_response_code); + LOG_TAG, + "Failed to post handler success response. Http response code: %ld. %s", + http_response_code, + resp.get_body().c_str()); return aws::http::response_code(http_response_code); } diff --git a/tests/unit/unit_tests.cpp b/tests/unit/unit_tests.cpp index acfef45..7483be5 100644 --- a/tests/unit/unit_tests.cpp +++ b/tests/unit/unit_tests.cpp @@ -103,6 +103,105 @@ TEST(InvocationResponseTest, constructor_based_failure) EXPECT_EQ(R"({"custom":"error"})", resp.get_payload()); } +// --- runtime_response tests --- + +TEST(RuntimeResponseTest, constructor_sets_all_fields) +{ + runtime_response resp("payload data", "application/json", "xray-trace-123"); + EXPECT_EQ("payload data", resp.get_payload()); + EXPECT_EQ("application/json", resp.get_content_type()); + EXPECT_EQ("xray-trace-123", resp.get_xray_response()); +} + +TEST(RuntimeResponseTest, constructor_with_empty_fields) +{ + runtime_response resp("", "", ""); + EXPECT_EQ("", resp.get_payload()); + EXPECT_EQ("", resp.get_content_type()); + EXPECT_EQ("", resp.get_xray_response()); +} + +TEST(RuntimeResponseTest, constructor_with_empty_xray) +{ + runtime_response resp("error body", "application/json", ""); + EXPECT_EQ("error body", resp.get_payload()); + EXPECT_EQ("application/json", resp.get_content_type()); + EXPECT_EQ("", resp.get_xray_response()); +} + +TEST(RuntimeResponseTest, large_payload) +{ + std::string large(10000, 'x'); + runtime_response resp(large, "text/plain", ""); + EXPECT_EQ(10000u, resp.get_payload().size()); + EXPECT_EQ(large, resp.get_payload()); +} + +TEST(RuntimeResponseTest, can_be_used_for_init_error) +{ + runtime_response init_err( + R"({"errorMessage":"module not found","errorType":"ImportError"})", "application/json", "xray-cause-data"); + EXPECT_EQ("application/json", init_err.get_content_type()); + EXPECT_NE(std::string::npos, init_err.get_payload().find("module not found")); + EXPECT_EQ("xray-cause-data", init_err.get_xray_response()); +} + +// --- invocation_response inheritance tests --- + +TEST(InvocationResponseInheritanceTest, is_a_runtime_response) +{ + invocation_response resp("payload", "text/plain", true, "xray"); + runtime_response const& base = resp; + EXPECT_EQ("payload", base.get_payload()); + EXPECT_EQ("text/plain", base.get_content_type()); + EXPECT_EQ("xray", base.get_xray_response()); +} + +TEST(InvocationResponseInheritanceTest, three_arg_constructor_has_empty_xray) +{ + invocation_response resp("data", "text/html", true); + EXPECT_EQ("data", resp.get_payload()); + EXPECT_EQ("text/html", resp.get_content_type()); + EXPECT_EQ("", resp.get_xray_response()); + EXPECT_TRUE(resp.is_success()); +} + +TEST(InvocationResponseInheritanceTest, four_arg_constructor_preserves_xray) +{ + invocation_response resp("err", "application/json", false, "xray-data"); + EXPECT_EQ("err", resp.get_payload()); + EXPECT_EQ("application/json", resp.get_content_type()); + EXPECT_EQ("xray-data", resp.get_xray_response()); + EXPECT_FALSE(resp.is_success()); +} + +TEST(InvocationResponseInheritanceTest, failure_with_xray_response) +{ + auto resp = invocation_response::failure("err msg", "ErrType", "xray-cause"); + EXPECT_FALSE(resp.is_success()); + EXPECT_EQ("application/json", resp.get_content_type()); + EXPECT_EQ("xray-cause", resp.get_xray_response()); + EXPECT_NE(std::string::npos, resp.get_payload().find("err msg")); +} + +TEST(InvocationResponseInheritanceTest, success_has_empty_xray) +{ + auto resp = invocation_response::success("ok", "text/plain"); + EXPECT_TRUE(resp.is_success()); + EXPECT_EQ("", resp.get_xray_response()); +} + +TEST(InvocationResponseInheritanceTest, can_pass_as_runtime_response_const_ref) +{ + invocation_response resp("body", "application/json", false, "xray-123"); + auto check = [](runtime_response const& r) { + EXPECT_EQ("body", r.get_payload()); + EXPECT_EQ("application/json", r.get_content_type()); + EXPECT_EQ("xray-123", r.get_xray_response()); + }; + check(resp); +} + // --- http::response tests --- TEST(HttpResponseTest, add_and_retrieve_header)