diff --git a/src/oatpp/codegen/codegen_define_ApiController_.hpp b/src/oatpp/codegen/codegen_define_ApiController_.hpp index c67f23c8..0e035871 100644 --- a/src/oatpp/codegen/codegen_define_ApiController_.hpp +++ b/src/oatpp/codegen/codegen_define_ApiController_.hpp @@ -21,7 +21,6 @@ * limitations under the License. * ***************************************************************************/ - #include "oatpp/core/macro/basic.hpp" #include "oatpp/core/macro/codegen.hpp" @@ -42,6 +41,12 @@ OATPP_MACRO_API_CONTROLLER_PARAM(OATPP_MACRO_API_CONTROLLER_HEADER, OATPP_MACRO_ #define PATH(TYPE, NAME, ...) \ OATPP_MACRO_API_CONTROLLER_PARAM(OATPP_MACRO_API_CONTROLLER_PATH, OATPP_MACRO_API_CONTROLLER_PATH_INFO, TYPE, NAME, (__VA_ARGS__)) +#define QUERIES(TYPE, NAME) \ +OATPP_MACRO_API_CONTROLLER_PARAM(OATPP_MACRO_API_CONTROLLER_QUERIES, OATPP_MACRO_API_CONTROLLER_QUERIES_INFO, TYPE, NAME, ()) + +#define QUERY(TYPE, NAME, ...) \ +OATPP_MACRO_API_CONTROLLER_PARAM(OATPP_MACRO_API_CONTROLLER_QUERY, OATPP_MACRO_API_CONTROLLER_QUERY_INFO, TYPE, NAME, (__VA_ARGS__)) + #define BODY_STRING(TYPE, NAME) \ OATPP_MACRO_API_CONTROLLER_PARAM(OATPP_MACRO_API_CONTROLLER_BODY_STRING, OATPP_MACRO_API_CONTROLLER_BODY_STRING_INFO, TYPE, NAME, ()) @@ -56,6 +61,7 @@ TYPE NAME = __request; #define OATPP_MACRO_API_CONTROLLER_REQUEST_INFO(TYPE, NAME, PARAM_LIST) + // HEADER MACRO // ------------------------------------------------------ #define OATPP_MACRO_API_CONTROLLER_HEADER_0(TYPE, NAME, PARAM_LIST) \ @@ -164,6 +170,67 @@ OATPP_MACRO_API_CONTROLLER_PATH_INFO_CHOOSER (TYPE, NAME, PARAM_LIST, HAS_ARGS) #define OATPP_MACRO_API_CONTROLLER_PATH_INFO(TYPE, NAME, PARAM_LIST) \ OATPP_MACRO_API_CONTROLLER_PATH_INFO_CHOOSER_EXP(TYPE, NAME, PARAM_LIST, OATPP_MACRO_HAS_ARGS PARAM_LIST); +// QUERIES MACRO // ------------------------------------------------------ + +#define OATPP_MACRO_API_CONTROLLER_QUERIES(TYPE, NAME, PARAM_LIST) \ +TYPE NAME = __request->getQueryParameters(); + +#define OATPP_MACRO_API_CONTROLLER_QUERIES_INFO(TYPE, NAME, PARAM_LIST) + +// QUERY MACRO // ------------------------------------------------------ + +#define OATPP_MACRO_API_CONTROLLER_QUERY_0(TYPE, NAME, PARAM_LIST) \ +auto __param_str_val_##NAME = __request->getQueryParameter(#NAME); \ +if(!__param_str_val_##NAME){ \ + return ApiController::handleError(Status::CODE_400, "Missing QUERY parameter '" #NAME "'"); \ +} \ +bool __param_validation_check_##NAME; \ +TYPE NAME = TYPE::Class::parseFromString(__param_str_val_##NAME, __param_validation_check_##NAME); \ +if(!__param_validation_check_##NAME){ \ + return ApiController::handleError(Status::CODE_400, "Invalid QUERY parameter '" #NAME "'. Expected type is '" #TYPE "'"); \ +} + +#define OATPP_MACRO_API_CONTROLLER_QUERY_1(TYPE, NAME, PARAM_LIST) \ +auto __param_str_val_##NAME = __request->getQueryParameter(OATPP_MACRO_FIRSTARG PARAM_LIST); \ +if(!__param_str_val_##NAME){ \ + return ApiController::handleError(Status::CODE_400, \ + oatpp::String("Missing QUERY parameter '") + OATPP_MACRO_FIRSTARG PARAM_LIST + "'"); \ +} \ +bool __param_validation_check_##NAME; \ +TYPE NAME = TYPE::Class::parseFromString(__param_str_val_##NAME, __param_validation_check_##NAME); \ +if(!__param_validation_check_##NAME){ \ + return ApiController::handleError(Status::CODE_400, \ + oatpp::String("Invalid QUERY parameter '") + \ + OATPP_MACRO_FIRSTARG PARAM_LIST + \ + "'. Expected type is '" #TYPE "'"); \ +} + +#define OATPP_MACRO_API_CONTROLLER_QUERY_CHOOSER(TYPE, NAME, PARAM_LIST, HAS_ARGS) \ +OATPP_MACRO_API_CONTROLLER_QUERY_##HAS_ARGS (TYPE, NAME, PARAM_LIST) + +#define OATPP_MACRO_API_CONTROLLER_QUERY_CHOOSER_EXP(TYPE, NAME, PARAM_LIST, HAS_ARGS) \ +OATPP_MACRO_API_CONTROLLER_QUERY_CHOOSER (TYPE, NAME, PARAM_LIST, HAS_ARGS) + +#define OATPP_MACRO_API_CONTROLLER_QUERY(TYPE, NAME, PARAM_LIST) \ +OATPP_MACRO_API_CONTROLLER_QUERY_CHOOSER_EXP(TYPE, NAME, PARAM_LIST, OATPP_MACRO_HAS_ARGS PARAM_LIST); + +// __INFO + +#define OATPP_MACRO_API_CONTROLLER_QUERY_INFO_0(TYPE, NAME, PARAM_LIST) \ +info->queryParams.push_back(Endpoint::Info::Param(#NAME, TYPE::Class::getType())); + +#define OATPP_MACRO_API_CONTROLLER_QUERY_INFO_1(TYPE, NAME, PARAM_LIST) \ +info->queryParams.push_back(Endpoint::Info::Param(OATPP_MACRO_FIRSTARG PARAM_LIST, TYPE::Class::getType())); + +#define OATPP_MACRO_API_CONTROLLER_QUERY_INFO_CHOOSER(TYPE, NAME, PARAM_LIST, HAS_ARGS) \ +OATPP_MACRO_API_CONTROLLER_QUERY_INFO_##HAS_ARGS (TYPE, NAME, PARAM_LIST) + +#define OATPP_MACRO_API_CONTROLLER_QUERY_INFO_CHOOSER_EXP(TYPE, NAME, PARAM_LIST, HAS_ARGS) \ +OATPP_MACRO_API_CONTROLLER_QUERY_INFO_CHOOSER (TYPE, NAME, PARAM_LIST, HAS_ARGS) + +#define OATPP_MACRO_API_CONTROLLER_QUERY_INFO(TYPE, NAME, PARAM_LIST) \ +OATPP_MACRO_API_CONTROLLER_QUERY_INFO_CHOOSER_EXP(TYPE, NAME, PARAM_LIST, OATPP_MACRO_HAS_ARGS PARAM_LIST); + // BODY_STRING MACRO // ------------------------------------------------------ #define OATPP_MACRO_API_CONTROLLER_BODY_STRING(TYPE, NAME, PARAM_LIST) \ diff --git a/src/oatpp/codegen/codegen_undef_ApiController_.hpp b/src/oatpp/codegen/codegen_undef_ApiController_.hpp index 29557299..2f37ed7b 100644 --- a/src/oatpp/codegen/codegen_undef_ApiController_.hpp +++ b/src/oatpp/codegen/codegen_undef_ApiController_.hpp @@ -33,6 +33,8 @@ #undef REQUEST #undef HEADER #undef PATH +#undef QUERIES +#undef QUERY #undef BODY_STRING #undef BODY_DTO @@ -77,6 +79,27 @@ #undef OATPP_MACRO_API_CONTROLLER_PATH_INFO_CHOOSER_EXP #undef OATPP_MACRO_API_CONTROLLER_PATH_INFO +// QUERIES MACRO // ------------------------------------------------------ + +#undef OATPP_MACRO_API_CONTROLLER_QUERIES +#undef OATPP_MACRO_API_CONTROLLER_QUERIES_INFO + +// QUERY MACRO // ------------------------------------------------------ + +#undef OATPP_MACRO_API_CONTROLLER_QUERY_0 +#undef OATPP_MACRO_API_CONTROLLER_QUERY_1 +#undef OATPP_MACRO_API_CONTROLLER_QUERY_CHOOSER +#undef OATPP_MACRO_API_CONTROLLER_QUERY_CHOOSER_EXP +#undef OATPP_MACRO_API_CONTROLLER_QUERY + +// __INFO + +#undef OATPP_MACRO_API_CONTROLLER_QUERY_INFO_0 +#undef OATPP_MACRO_API_CONTROLLER_QUERY_INFO_1 +#undef OATPP_MACRO_API_CONTROLLER_QUERY_INFO_CHOOSER +#undef OATPP_MACRO_API_CONTROLLER_QUERY_INFO_CHOOSER_EXP +#undef OATPP_MACRO_API_CONTROLLER_QUERY_INFO + // BODY_STRING MACRO // ------------------------------------------------------ #undef OATPP_MACRO_API_CONTROLLER_BODY_STRING diff --git a/src/oatpp/network/Url.hpp b/src/oatpp/network/Url.hpp index a919b322..7633e04a 100644 --- a/src/oatpp/network/Url.hpp +++ b/src/oatpp/network/Url.hpp @@ -33,7 +33,9 @@ #include namespace oatpp { namespace network { - + + +// TODO - refactor to use oatpp::data::share::MemoryLabel class Url : public oatpp::base::Controllable { public: typedef oatpp::data::share::StringKeyLabel StringKeyLabel; diff --git a/src/oatpp/web/protocol/http/Http.hpp b/src/oatpp/web/protocol/http/Http.hpp index 83f07bfc..d35ffc81 100644 --- a/src/oatpp/web/protocol/http/Http.hpp +++ b/src/oatpp/web/protocol/http/Http.hpp @@ -263,6 +263,7 @@ struct ResponseStartingLine { class Protocol { public: typedef std::unordered_map Headers; + typedef std::unordered_map QueryParams; private: static oatpp::data::share::StringKeyLabelCI_FAST parseHeaderNameLabel(const std::shared_ptr& headersText, oatpp::parser::Caret& caret); diff --git a/src/oatpp/web/protocol/http/incoming/Request.cpp b/src/oatpp/web/protocol/http/incoming/Request.cpp index 3a7ffd0f..fa0ab7dd 100644 --- a/src/oatpp/web/protocol/http/incoming/Request.cpp +++ b/src/oatpp/web/protocol/http/incoming/Request.cpp @@ -36,6 +36,7 @@ Request::Request(const http::RequestStartingLine& startingLine, , m_headers(headers) , m_bodyStream(bodyStream) , m_bodyDecoder(bodyDecoder) + , m_queryParamsParsed(false) {} std::shared_ptr Request::createShared(const http::RequestStartingLine& startingLine, @@ -58,6 +59,28 @@ const http::Protocol::Headers& Request::getHeaders() const { return m_headers; } +const http::Protocol::QueryParams& Request::getQueryParameters() const { + if(!m_queryParamsParsed) { + m_queryParams = oatpp::network::Url::Parser::labelQueryParams(m_pathVariables.getTail()); + m_queryParamsParsed = true; + } + return m_queryParams; +} + +oatpp::String Request::getQueryParameter(const oatpp::data::share::StringKeyLabel& name) const { + auto iter = getQueryParameters().find(name); + if (iter == getQueryParameters().end()) { + return nullptr; + } else { + return iter->second.toString(); + } +} + +oatpp::String Request::getQueryParameter(const oatpp::data::share::StringKeyLabel& name, const oatpp::String& defaultValue) const { + auto value = getQueryParameter(name); + return value ? value : defaultValue; +} + std::shared_ptr Request::getBodyStream() const { return m_bodyStream; } diff --git a/src/oatpp/web/protocol/http/incoming/Request.hpp b/src/oatpp/web/protocol/http/incoming/Request.hpp index 11c0a738..ae5ca47f 100644 --- a/src/oatpp/web/protocol/http/incoming/Request.hpp +++ b/src/oatpp/web/protocol/http/incoming/Request.hpp @@ -28,6 +28,7 @@ #include "oatpp/web/protocol/http/Http.hpp" #include "oatpp/web/protocol/http/incoming/BodyDecoder.hpp" #include "oatpp/web/url/mapping/Pattern.hpp" +#include "oatpp/network/Url.hpp" namespace oatpp { namespace web { namespace protocol { namespace http { namespace incoming { @@ -39,6 +40,7 @@ public: OBJECT_POOL(Incoming_Request_Pool, Request, 32) SHARED_OBJECT_POOL(Shared_Incoming_Request_Pool, Request, 32) private: + http::RequestStartingLine m_startingLine; url::mapping::Pattern::MatchMap m_pathVariables; http::Protocol::Headers m_headers; @@ -49,6 +51,10 @@ private: * Custom BodyDecoder can be set on demand */ std::shared_ptr m_bodyDecoder; + + mutable bool m_queryParamsParsed; // used for lazy parsing of QueryParams + mutable http::Protocol::QueryParams m_queryParams; + public: Request(const http::RequestStartingLine& startingLine, @@ -64,6 +70,29 @@ public: const std::shared_ptr& bodyStream, const std::shared_ptr& bodyDecoder); + /** + * Get map of url query parameters. + * Query parameters will be lazy parsed from url "tail" + * Please note: lazy parsing of query parameters is not thread-safe! + * @return map for "&key=value" + */ + const http::Protocol::QueryParams& getQueryParameters() const; + + /** + * Get query parameter value by name + * @param name + * @return query parameter value + */ + oatpp::String getQueryParameter(const oatpp::data::share::StringKeyLabel& name) const; + + /** + * + * @param name + * @param defaultValue + * @return query parameter value or defaultValue if no such parameter found + */ + oatpp::String getQueryParameter(const oatpp::data::share::StringKeyLabel& name, const oatpp::String& defaultValue) const; + /** * Get request starting line. (method, path, protocol) * @return starting line structure diff --git a/src/oatpp/web/server/api/ApiController.hpp b/src/oatpp/web/server/api/ApiController.hpp index a6ef30f3..d5a10258 100644 --- a/src/oatpp/web/server/api/ApiController.hpp +++ b/src/oatpp/web/server/api/ApiController.hpp @@ -52,6 +52,7 @@ public: typedef oatpp::web::protocol::http::outgoing::Response OutgoingResponse; typedef oatpp::web::protocol::http::Status Status; typedef oatpp::web::protocol::http::Header Header; + typedef oatpp::web::protocol::http::Protocol::QueryParams QueryParams; typedef oatpp::web::server::api::Endpoint Endpoint; typedef oatpp::collection::LinkedList> Endpoints; diff --git a/test/oatpp/web/FullTest.cpp b/test/oatpp/web/FullTest.cpp index 905a259d..cfb8b974 100644 --- a/test/oatpp/web/FullTest.cpp +++ b/test/oatpp/web/FullTest.cpp @@ -131,6 +131,26 @@ void FullTest::onRun() { OATPP_ASSERT(dto->testValue == "my_test_param"); } + { // test GET with query parameters + auto response = client->getWithQueries("oatpp", 1, connection); + OATPP_ASSERT(response->getStatusCode() == 200); + auto dto = response->readBodyToDto(objectMapper); + OATPP_ASSERT(dto); + OATPP_ASSERT(dto->testValue == "name=oatpp&age=1"); + } + + { // test GET with query parameters + auto response = client->getWithQueriesMap("value1", 32, 0.32, connection); + OATPP_ASSERT(response->getStatusCode() == 200); + auto dto = response->readBodyToDto(objectMapper); + OATPP_ASSERT(dto); + OATPP_ASSERT(dto->testMap); + OATPP_ASSERT(dto->testMap->count() == 3); + OATPP_ASSERT(dto->testMap->get("key1", "") == "value1"); + OATPP_ASSERT(dto->testMap->get("key2", "") == "32"); + OATPP_ASSERT(dto->testMap->get("key3", "") == oatpp::utils::conversion::float32ToStr(0.32)); + } + { // test GET with header parameter auto response = client->getWithHeaders("my_test_header", connection); OATPP_ASSERT(response->getStatusCode() == 200); diff --git a/test/oatpp/web/app/Client.hpp b/test/oatpp/web/app/Client.hpp index 862fd8ef..14efb5ac 100644 --- a/test/oatpp/web/app/Client.hpp +++ b/test/oatpp/web/app/Client.hpp @@ -37,6 +37,8 @@ class Client : public oatpp::web::client::ApiClient { API_CALL("GET", "/", getRoot) API_CALL("GET", "params/{param}", getWithParams, PATH(String, param)) + API_CALL("GET", "queries", getWithQueries, QUERY(String, name), QUERY(Int32, age)) + API_CALL("GET", "queries/map", getWithQueriesMap, QUERY(String, key1), QUERY(Int32, key2), QUERY(Float32, key3)) API_CALL("GET", "headers", getWithHeaders, HEADER(String, param, "X-TEST-HEADER")) API_CALL("POST", "body", postBody, BODY_STRING(String, body)) API_CALL("POST", "echo", echoBody, BODY_STRING(String, body)) diff --git a/test/oatpp/web/app/Controller.hpp b/test/oatpp/web/app/Controller.hpp index f3aa1aa0..5b5c07bd 100644 --- a/test/oatpp/web/app/Controller.hpp +++ b/test/oatpp/web/app/Controller.hpp @@ -28,9 +28,12 @@ #include "./DTOs.hpp" #include "oatpp/web/server/api/ApiController.hpp" #include "oatpp/parser/json/mapping/ObjectMapper.hpp" +#include "oatpp/core/utils/ConversionUtils.hpp" #include "oatpp/core/macro/codegen.hpp" #include "oatpp/core/macro/component.hpp" +#include + namespace oatpp { namespace test { namespace web { namespace app { class Controller : public oatpp::web::server::api::ApiController { @@ -61,6 +64,23 @@ public: return createDtoResponse(Status::CODE_200, dto); } + ENDPOINT("GET", "queries", getWithQueries, + QUERY(String, name), QUERY(Int32, age)) { + auto dto = TestDto::createShared(); + dto->testValue = "name=" + name + "&age=" + oatpp::utils::conversion::int32ToStr(age->getValue()); + return createDtoResponse(Status::CODE_200, dto); + } + + ENDPOINT("GET", "queries/map", getWithQueriesMap, + QUERIES(QueryParams, queries)) { + auto dto = TestDto::createShared(); + dto->testMap = dto->testMap->createShared(); + for(auto& it : queries) { + dto->testMap->put(it.first.toString(), it.second.toString()); + } + return createDtoResponse(Status::CODE_200, dto); + } + ENDPOINT("GET", "headers", getWithHeaders, HEADER(String, param, "X-TEST-HEADER")) { //OATPP_LOGD(TAG, "GET headers {X-TEST-HEADER: %s}", param->c_str()); diff --git a/test/oatpp/web/app/DTOs.hpp b/test/oatpp/web/app/DTOs.hpp index f91af937..18b05634 100644 --- a/test/oatpp/web/app/DTOs.hpp +++ b/test/oatpp/web/app/DTOs.hpp @@ -37,6 +37,7 @@ class TestDto : public oatpp::data::mapping::type::Object { DTO_INIT(TestDto, Object) DTO_FIELD(String, testValue); + DTO_FIELD(Fields::ObjectWrapper, testMap); };