Merge pull request #46 from oatpp/merge_server_side_query_params_mapping

Merge server side query params mapping
This commit is contained in:
Leonid Stryzhevskyi 2019-03-01 18:54:00 +02:00 committed by GitHub
commit 6f7b2dcbc1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 236 additions and 6 deletions

View File

@ -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.add(#NAME, TYPE::Class::getType());
#define OATPP_MACRO_API_CONTROLLER_QUERY_INFO_1(TYPE, NAME, PARAM_LIST) \
info->queryParams.add(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) \

View File

@ -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

View File

@ -33,7 +33,9 @@
#include <unordered_map>
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;

View File

@ -263,6 +263,7 @@ struct ResponseStartingLine {
class Protocol {
public:
typedef std::unordered_map<oatpp::data::share::StringKeyLabelCI_FAST, oatpp::data::share::StringKeyLabel> Headers;
typedef std::unordered_map<oatpp::data::share::StringKeyLabel, oatpp::data::share::StringKeyLabel> QueryParams;
private:
static oatpp::data::share::StringKeyLabelCI_FAST parseHeaderNameLabel(const std::shared_ptr<oatpp::base::StrBuffer>& headersText,
oatpp::parser::Caret& caret);

View File

@ -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> 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<oatpp::data::stream::InputStream> Request::getBodyStream() const {
return m_bodyStream;
}

View File

@ -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<const http::incoming::BodyDecoder> 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<oatpp::data::stream::InputStream>& bodyStream,
const std::shared_ptr<const http::incoming::BodyDecoder>& 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<key, value> 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

View File

@ -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<std::shared_ptr<Endpoint>> Endpoints;

View File

@ -89,6 +89,17 @@ public:
*/
Param& add(const oatpp::String& name, oatpp::data::mapping::type::Type* type);
/**
* Add parameter name to list order
* @tparam T
* @param name
* @return new or existing parameter
*/
template<class T>
Param& add(const oatpp::String& name) {
return add(name, T::Class::getType());
}
/**
* Get or add param by name
* @param name

View File

@ -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<app::TestDto>(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<app::TestDto>(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);

View File

@ -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))

View File

@ -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 <sstream>
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());

View File

@ -37,6 +37,7 @@ class TestDto : public oatpp::data::mapping::type::Object {
DTO_INIT(TestDto, Object)
DTO_FIELD(String, testValue);
DTO_FIELD(Fields<String>::ObjectWrapper, testMap);
};

View File

@ -56,8 +56,8 @@ public:
ENDPOINT_INFO(pathParams) {
info->pathParams["param1"].description = "this is param1";
info->queryParams.add("q1", String::Class::getType()).description = "query param";
info->headers.add("X-TEST-HEADER", String::Class::getType()).description = "TEST-HEADER-PARAM";
info->queryParams.add<String>("q1").description = "query param";
info->headers.add<String>("X-TEST-HEADER").description = "TEST-HEADER-PARAM";
}
ENDPOINT("GET", "path/{param1}/{param2}", pathParams,
PATH(String, param1),
@ -65,6 +65,15 @@ public:
return createResponse(Status::CODE_200, "test2");
}
ENDPOINT_INFO(queryParams) {
info->queryParams["param1"].description = "this is param1";
}
ENDPOINT("GET", "query", queryParams,
QUERY(String, param1),
QUERY(String, param2)) {
return createResponse(Status::CODE_200, "test3");
}
#include OATPP_CODEGEN_END(ApiController)
};
@ -99,7 +108,7 @@ void ApiControllerTest::onRun() {
auto stream = oatpp::data::stream::ChunkedBuffer::createShared();
response->send(stream);
OATPP_LOGD(TAG, "response=\n---\n%s\n---\n", stream->toString()->c_str());
OATPP_LOGD(TAG, "response:\n---\n%s\n---\n", stream->toString()->c_str());
}
@ -126,7 +135,28 @@ void ApiControllerTest::onRun() {
auto stream = oatpp::data::stream::ChunkedBuffer::createShared();
response->send(stream);
OATPP_LOGD(TAG, "response=\n---\n%s\n---\n", stream->toString()->c_str());
OATPP_LOGD(TAG, "response:\n---\n%s\n---\n", stream->toString()->c_str());
}
{
auto endpoint = controller.Z__ENDPOINT_queryParams;
OATPP_ASSERT(endpoint);
OATPP_ASSERT(!endpoint->info->summary);
OATPP_ASSERT(endpoint->info->queryParams["param1"].name == "param1");
OATPP_ASSERT(endpoint->info->queryParams["param1"].description == "this is param1");
OATPP_ASSERT(endpoint->info->queryParams["param2"].name == "param2");
OATPP_ASSERT(!endpoint->info->queryParams["param2"].description);
auto response = controller.queryParams("p1", "p2");
OATPP_ASSERT(response->getStatus().code == 200);
auto stream = oatpp::data::stream::ChunkedBuffer::createShared();
response->send(stream);
OATPP_LOGD(TAG, "response:\n---\n%s\n---\n", stream->toString()->c_str());
}