/*************************************************************************** * * Project _____ __ ____ _ _ * ( _ ) /__\ (_ _)_| |_ _| |_ * )(_)( /(__)\ )( (_ _)(_ _) * (_____)(__)(__)(__) |_| |_| * * * Copyright 2018-present, Leonid Stryzhevskyi * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ***************************************************************************/ #ifndef oatpp_web_server_api_Controller_hpp #define oatpp_web_server_api_Controller_hpp #include "./Endpoint.hpp" #include "oatpp/web/server/handler/AuthorizationHandler.hpp" #include "oatpp/web/server/handler/ErrorHandler.hpp" #include "oatpp/web/server/handler/AuthorizationHandler.hpp" #include "oatpp/web/protocol/http/incoming/Response.hpp" #include "oatpp/web/protocol/http/outgoing/Request.hpp" #include "oatpp/web/protocol/http/outgoing/ResponseFactory.hpp" #include "oatpp/core/utils/ConversionUtils.hpp" #include #include namespace oatpp { namespace web { namespace server { namespace api { /** * Class responsible for implementation and management of endpoints.
* For details see [ApiController](https://oatpp.io/docs/components/api-controller/). */ class ApiController : public oatpp::base::Countable { protected: typedef ApiController __ControllerType; public: /** * Convenience typedef for &id:oatpp::web::protocol::http::outgoing::ResponseFactory;. */ typedef oatpp::web::protocol::http::outgoing::ResponseFactory ResponseFactory; /** * Convenience typedef for &id:oatpp::web::protocol::http::incoming::Request;. */ typedef oatpp::web::protocol::http::incoming::Request IncomingRequest; /** * Convenience typedef for &id:oatpp::web::protocol::http::outgoing::Request;. */ typedef oatpp::web::protocol::http::outgoing::Request OutgoingRequest; /** * Convenience typedef for &id:oatpp::web::protocol::http::incoming::Response;. */ typedef oatpp::web::protocol::http::incoming::Response IncomingResponse; /** * Convenience typedef for &id:oatpp::web::protocol::http::outgoing::Response;. */ typedef oatpp::web::protocol::http::outgoing::Response OutgoingResponse; /** * Convenience typedef for &id:oatpp::web::protocol::http::Status;. */ typedef oatpp::web::protocol::http::Status Status; /** * Convenience typedef for &id:oatpp::web::protocol::http::Header;. */ typedef oatpp::web::protocol::http::Header Header; /** * Convenience typedef for &id:oatpp::web::protocol::http::QueryParams;. */ typedef oatpp::web::protocol::http::QueryParams QueryParams; /** * Convenience typedef for &id:oatpp::web::server::HttpRequestHandler;. */ typedef oatpp::web::server::HttpRequestHandler RequestHandler; /** * Convenience typedef for &id:oatpp::web::server::handler::AuthorizationHandler;. */ typedef oatpp::web::server::handler::AuthorizationHandler AuthorizationHandler; public: /** * Convenience typedef for &id:oatpp::data::mapping::ObjectMapper;. */ typedef oatpp::data::mapping::ObjectMapper ObjectMapper; /** * Convenience typedef for &id:oatpp::data::mapping::type::String;. */ typedef oatpp::String String; /** * Convenience typedef for &id:oatpp::data::mapping::type::Int8;. */ typedef oatpp::Int8 Int8; /** * Convenience typedef for &id:oatpp::data::mapping::type::UInt8;. */ typedef oatpp::UInt8 UInt8; /** * Convenience typedef for &id:oatpp::data::mapping::type::Int16;. */ typedef oatpp::Int16 Int16; /** * Convenience typedef for &id:oatpp::data::mapping::type::UInt16;. */ typedef oatpp::UInt16 UInt16; /** * Convenience typedef for &id:oatpp::data::mapping::type::Int32;. */ typedef oatpp::Int32 Int32; /** * Convenience typedef for &id:oatpp::data::mapping::type::UInt32;. */ typedef oatpp::UInt32 UInt32; /** * Convenience typedef for &id:oatpp::data::mapping::type::Int64;. */ typedef oatpp::Int64 Int64; /** * Convenience typedef for &id:oatpp::data::mapping::type::UInt64;. */ typedef oatpp::UInt64 UInt64; /** * Convenience typedef for &id:oatpp::data::mapping::type::Float32;. */ typedef oatpp::Float32 Float32; /** * Convenience typedef for &id:atpp::data::mapping::type::Float64;. */ typedef oatpp::Float64 Float64; /** * Convenience typedef for &id:oatpp::data::mapping::type::Boolean;. */ typedef oatpp::Boolean Boolean; /* * Convenience typedef for std::function()>. */ typedef std::function()> EndpointInfoBuilder; template using Object = oatpp::Object; template using List = oatpp::List; template using Fields = oatpp::Fields; template using Enum = oatpp::data::mapping::type::Enum; protected: /* * Endpoint Coroutine base class */ template class HandlerCoroutine : public oatpp::async::CoroutineWithResult&> { public: HandlerCoroutine(ControllerT* pController, const std::shared_ptr& pRequest) : controller(pController) , request(pRequest) {} ControllerT* const controller; std::shared_ptr request; }; /* * Handler which subscribes to specific URL in Router and delegates calls endpoints */ template class Handler : public RequestHandler { public: typedef std::shared_ptr (T::*Method)(const std::shared_ptr&); typedef oatpp::async::CoroutineStarterForResult&> (T::*MethodAsync)(const std::shared_ptr&); private: class ErrorHandlingCoroutine : public oatpp::async::CoroutineWithResult&> { private: Handler* m_handler; std::shared_ptr m_request; public: ErrorHandlingCoroutine(Handler* handler, const std::shared_ptr& request) : m_handler(handler) , m_request(request) {} async::Action act() override { return (m_handler->m_controller->*m_handler->m_methodAsync)(m_request) .callbackTo(&ErrorHandlingCoroutine::onResponse); } async::Action onResponse(const std::shared_ptr& response) { return this->_return(response); } async::Action handleError(async::Error* error) override { auto eptr = std::make_exception_ptr(*error); auto response = m_handler->m_controller->m_errorHandler->handleError(eptr); return this->_return(response); } }; private: T* m_controller; Method m_method; MethodAsync m_methodAsync; public: Handler(T* controller, Method method, MethodAsync methodAsync) : m_controller(controller) , m_method(method) , m_methodAsync(methodAsync) {} public: static std::shared_ptr createShared(T* controller, Method method, MethodAsync methodAsync){ return std::make_shared(controller, method, methodAsync); } std::shared_ptr handle(const std::shared_ptr& request) override { if(m_method == nullptr) { if(m_methodAsync == nullptr) { throw protocol::http::HttpError(Status::CODE_500, "[ApiController]: Error. Handler method is nullptr.");; } throw protocol::http::HttpError(Status::CODE_500, "[ApiController]: Error. Non-async call to async endpoint.");; } try { return (m_controller->*m_method)(request); } catch (...) { auto response = m_controller->handleError(std::current_exception()); if(response != nullptr) { return response; } throw; } } oatpp::async::CoroutineStarterForResult&> handleAsync(const std::shared_ptr& request) override { if(m_methodAsync == nullptr) { if(m_method == nullptr) { throw oatpp::web::protocol::http::HttpError(Status::CODE_500, "[ApiController]: Error. Handler method is nullptr."); } throw oatpp::web::protocol::http::HttpError(Status::CODE_500, "[ApiController]: Error. Async call to non-async endpoint."); } if(m_controller->m_errorHandler) { return ErrorHandlingCoroutine::startForResult(this, request); } return (m_controller->*m_methodAsync)(request); } Method setMethod(Method method) { auto prev = m_method; m_method = method; return prev; } Method getMethod() { return m_method; } MethodAsync setMethodAsync(MethodAsync methodAsync) { auto prev = m_methodAsync; m_methodAsync = methodAsync; return prev; } MethodAsync getMethodAsync() { return m_methodAsync; } }; protected: /* * Set endpoint info by endpoint name. (Endpoint name is the 'NAME' parameter of the ENDPOINT macro) * Info should be set before call to addEndpointsToRouter(); */ void setEndpointInfo(const std::string& endpointName, const std::shared_ptr& info); /* * Get endpoint info by endpoint name. (Endpoint name is the 'NAME' parameter of the ENDPOINT macro) */ std::shared_ptr getEndpointInfo(const std::string& endpointName); /* * Set endpoint Request handler. * @param endpointName * @param handler */ void setEndpointHandler(const std::string& endpointName, const std::shared_ptr& handler); /* * Get endpoint Request handler. * @param endpointName * @return */ std::shared_ptr getEndpointHandler(const std::string& endpointName); protected: Endpoints m_endpoints; std::shared_ptr m_errorHandler; std::shared_ptr m_defaultAuthorizationHandler; std::shared_ptr m_defaultObjectMapper; std::unordered_map> m_endpointInfo; std::unordered_map> m_endpointHandlers; const oatpp::String m_routerPrefix; public: ApiController(const std::shared_ptr& defaultObjectMapper, const oatpp::String &routerPrefix = nullptr) : m_defaultObjectMapper(defaultObjectMapper) , m_routerPrefix(routerPrefix) {} public: template static std::shared_ptr createEndpoint(Endpoints& endpoints, const std::shared_ptr>& handler, const EndpointInfoBuilder& infoBuilder) { auto endpoint = Endpoint::createShared(handler, infoBuilder); endpoints.append(endpoint); return endpoint; } /** * Get list of Endpoints created via ENDPOINT macro */ const Endpoints& getEndpoints(); /** * Set error handler to handle errors that occur during the endpoint's execution */ void setErrorHandler(const std::shared_ptr& errorHandler); /** * Handle the exception using the registered ErrorHandler or if no handler has been set, uses the DefaultErrorHandler::handleError * @note Does not rethrow an exception anymore, OutgoingResponse has to be returned by the caller! * @note If this handler fails to handle the exception, it will be handled by the connection handlers ErrorHandler. */ std::shared_ptr handleError(const std::exception_ptr& exceptionPtr) const; /** * [under discussion] * Set authorization handler to handle calls to handleAuthorization. * Must be called before controller is added to a router or swagger-doc if an endpoint uses the AUTHORIZATION macro */ void setDefaultAuthorizationHandler(const std::shared_ptr& authorizationHandler); /** * Get authorization handler. * @return */ std::shared_ptr getDefaultAuthorizationHandler(); /** * [under discussion] * Do not use it directly. This method is under discussion. * Currently returns AuthorizationObject created by AuthorizationHandler or return DefaultAuthorizationObject by DefaultAuthorizationHandler if AuthorizationHandler is null */ std::shared_ptr handleDefaultAuthorization(const String &authHeader) const; const std::shared_ptr& getDefaultObjectMapper() const; // Helper methods std::shared_ptr createResponse(const Status& status, const oatpp::String& str) const; std::shared_ptr createResponse(const Status& status) const; std::shared_ptr createDtoResponse(const Status& status, const oatpp::Void& dto, const std::shared_ptr& objectMapper) const; std::shared_ptr createDtoResponse(const Status& status, const oatpp::Void& dto) const; public: template struct TypeInterpretation { static T fromString(const oatpp::String& typeName, const oatpp::String& text, bool& success) { (void) text; success = false; OATPP_LOGE("[oatpp::web::server::api::ApiController::TypeInterpretation::fromString()]", "Error. No conversion from '%s' to '%s' is defined.", "oatpp::String", typeName->c_str()); throw std::runtime_error("[oatpp::web::server::api::ApiController::TypeInterpretation::fromString()]: Error. " "No conversion from 'oatpp::String' to '" + *typeName + "' is defined. " "Please define type conversion."); } }; }; template<> struct ApiController::TypeInterpretation { static oatpp::String fromString(const oatpp::String& typeName, const oatpp::String& text, bool& success) { (void) typeName; success = true; return text; } }; template<> struct ApiController::TypeInterpretation { static oatpp::Int8 fromString(const oatpp::String& typeName, const oatpp::String& text, bool& success) { (void) typeName; //TODO: check the range and perhaps throw an exception if the variable doesn't fit return static_cast(utils::conversion::strToInt32(text, success)); } }; template<> struct ApiController::TypeInterpretation { static oatpp::UInt8 fromString(const oatpp::String& typeName, const oatpp::String& text, bool& success) { (void) typeName; //TODO: check the range and perhaps throw an exception if the variable doesn't fit return static_cast(utils::conversion::strToUInt32(text, success)); } }; template<> struct ApiController::TypeInterpretation { static oatpp::Int16 fromString(const oatpp::String& typeName, const oatpp::String& text, bool& success) { (void) typeName; //TODO: check the range and perhaps throw an exception if the variable doesn't fit return static_cast(utils::conversion::strToInt32(text, success)); } }; template<> struct ApiController::TypeInterpretation { static oatpp::UInt16 fromString(const oatpp::String& typeName, const oatpp::String& text, bool& success) { (void) typeName; //TODO: check the range and perhaps throw an exception if the variable doesn't fit return static_cast(utils::conversion::strToUInt32(text, success)); } }; template<> struct ApiController::TypeInterpretation { static oatpp::Int32 fromString(const oatpp::String& typeName, const oatpp::String& text, bool& success) { (void) typeName; return utils::conversion::strToInt32(text, success); } }; template<> struct ApiController::TypeInterpretation { static oatpp::UInt32 fromString(const oatpp::String& typeName, const oatpp::String& text, bool& success) { (void) typeName; return utils::conversion::strToUInt32(text, success); } }; template<> struct ApiController::TypeInterpretation { static oatpp::Int64 fromString(const oatpp::String& typeName, const oatpp::String& text, bool& success) { (void) typeName; return utils::conversion::strToInt64(text, success); } }; template<> struct ApiController::TypeInterpretation { static oatpp::UInt64 fromString(const oatpp::String& typeName, const oatpp::String& text, bool& success) { (void) typeName; return utils::conversion::strToUInt64(text, success); } }; template<> struct ApiController::TypeInterpretation { static oatpp::Float32 fromString(const oatpp::String& typeName, const oatpp::String& text, bool& success) { (void) typeName; return utils::conversion::strToFloat32(text, success); } }; template<> struct ApiController::TypeInterpretation { static oatpp::Float64 fromString(const oatpp::String& typeName, const oatpp::String& text, bool& success) { (void) typeName; return utils::conversion::strToFloat64(text, success); } }; template<> struct ApiController::TypeInterpretation { static oatpp::Boolean fromString(const oatpp::String& typeName, const oatpp::String& text, bool& success) { (void) typeName; return utils::conversion::strToBool(text, success); } }; template struct ApiController::TypeInterpretation > { typedef data::mapping::type::EnumObjectWrapper EnumOW; typedef typename I::UnderlyingTypeObjectWrapper UTOW; static EnumOW fromString(const oatpp::String& typeName, const oatpp::String& text, bool& success) { const auto& parsedValue = ApiController::TypeInterpretation::fromString(typeName, text, success); if(success) { data::mapping::type::EnumInterpreterError error = data::mapping::type::EnumInterpreterError::OK; const auto& result = I::fromInterpretation(parsedValue, error); if(error == data::mapping::type::EnumInterpreterError::OK) { return result.template cast(); } success = false; } return nullptr; } }; }}}} #endif /* oatpp_web_server_api_Controller_hpp */