Skip to content

Commit 8f56395

Browse files
authored
[C++] [Pistache] Model validation, general overhaul (#9251)
* overhaul pistache templates * fix function signature in model-source return type now aligns with definition in model-header * use default keyword for destructors * generate pistache samples * move bin/configs/other/cpp-pistache-server-cpp-pistache.yaml to bin/configs/cpp-pistache-server-cpp-pistache.yaml * Only generate validation body if necessary * generate pistache samples
1 parent 4d2b022 commit 8f56395

40 files changed

Lines changed: 1461 additions & 537 deletions
File renamed without changes.

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CppPistacheServerCodegen.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ public CppPistacheServerCodegen() {
128128
typeMapping.put("boolean", "bool");
129129
typeMapping.put("array", "std::vector");
130130
typeMapping.put("map", "std::map");
131+
typeMapping.put("set", "std::vector");
131132
typeMapping.put("file", "std::string");
132133
typeMapping.put("object", "Object");
133134
typeMapping.put("binary", "std::string");

modules/openapi-generator/src/main/resources/cpp-pistache-server/api-header.mustache

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,21 @@
1414
#include <pistache/http_headers.h>
1515
#include <pistache/optional.h>
1616
{{^hasModelImport}}#include <nlohmann/json.hpp>{{/hasModelImport}}
17+
#include <utility>
1718

1819
{{#imports}}{{{import}}}
1920
{{/imports}}
2021

21-
{{#apiNamespaceDeclarations}}
22-
namespace {{this}} {
23-
{{/apiNamespaceDeclarations}}
24-
25-
{{#hasModelImport}}
26-
using namespace {{modelNamespace}};{{/hasModelImport}}
22+
namespace {{apiNamespace}}
23+
{
2724
2825
class {{declspec}} {{classname}} {
2926
public:
30-
{{classname}}(std::shared_ptr<Pistache::Rest::Router>);
31-
virtual ~{{classname}}() {}
27+
explicit {{classname}}(const std::shared_ptr<Pistache::Rest::Router>& rtr);
28+
virtual ~{{classname}}() = default;
3229
void init();
3330

34-
const std::string base = "{{basePathWithoutHost}}";
31+
static const std::string base;
3532

3633
private:
3734
void setupRoutes();
@@ -41,9 +38,21 @@ private:
4138
{{/operation}}
4239
void {{classnameSnakeLowerCase}}_default_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response);
4340

44-
std::shared_ptr<Pistache::Rest::Router> router;
45-
{{#operation}}
41+
const std::shared_ptr<Pistache::Rest::Router> router;
42+
43+
/// <summary>
44+
/// Helper function to handle unexpected Exceptions during Parameter parsing and validation.
45+
/// May be overriden to return custom error formats.
46+
/// </summary>
47+
virtual std::pair<Pistache::Http::Code, std::string> handleParsingException(const std::exception& ex) const noexcept;
4648

49+
/// <summary>
50+
/// Helper function to handle unexpected Exceptions during processing of the request in handler functions.
51+
/// May be overriden to return custom error formats.
52+
/// </summary>
53+
virtual std::pair<Pistache::Http::Code, std::string> handleOperationException(const std::exception& ex) const noexcept;
54+
55+
{{#operation}}
4756
/// <summary>
4857
/// {{summary}}
4958
/// </summary>
@@ -54,7 +63,7 @@ private:
5463
{{#allParams}}
5564
/// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}</param>
5665
{{/allParams}}
57-
virtual void {{operationIdSnakeCase}}({{#allParams}}const {{{dataType}}} &{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}Pistache::Http::ResponseWriter &response) = 0;
66+
virtual void {{operationIdSnakeCase}}({{#allParams}}const {{#isModel}}{{modelNamespace}}::{{/isModel}}{{{dataType}}} &{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}Pistache::Http::ResponseWriter &response) = 0;
5867
{{/vendorExtensions.x-codegen-pistache-is-parsing-supported}}
5968
{{^vendorExtensions.x-codegen-pistache-is-parsing-supported}}
6069
virtual void {{operationIdSnakeCase}}(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter &response) = 0;
@@ -63,9 +72,7 @@ private:
6372

6473
};
6574

66-
{{#apiNamespaceDeclarations}}
67-
}
68-
{{/apiNamespaceDeclarations}}
75+
} // namespace {{apiNamespace}}
6976

7077
#endif /* {{classname}}_H_ */
7178

modules/openapi-generator/src/main/resources/cpp-pistache-server/api-impl-header.mustache

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,16 @@
2222
{{#imports}}{{{import}}}
2323
{{/imports}}
2424

25-
{{#apiNamespaceDeclarations}}
26-
namespace {{this}} {
27-
{{/apiNamespaceDeclarations}}
25+
namespace {{apiNamespace}}
26+
{
2827
2928
{{#hasModelImport}}
3029
using namespace {{modelNamespace}};{{/hasModelImport}}
3130

3231
class {{classname}}Impl : public {{apiNamespace}}::{{classname}} {
3332
public:
34-
{{classname}}Impl(std::shared_ptr<Pistache::Rest::Router>);
35-
~{{classname}}Impl() {}
33+
explicit {{classname}}Impl(const std::shared_ptr<Pistache::Rest::Router>& rtr);
34+
~{{classname}}Impl() override = default;
3635

3736
{{#operation}}
3837
{{#vendorExtensions.x-codegen-pistache-is-parsing-supported}}
@@ -45,11 +44,9 @@ public:
4544

4645
};
4746

48-
{{#apiNamespaceDeclarations}}
49-
}
50-
{{/apiNamespaceDeclarations}}
47+
} // namespace {{apiNamespace}}
5148

5249
{{/operations}}
5350

5451

55-
#endif
52+
#endif

modules/openapi-generator/src/main/resources/cpp-pistache-server/api-impl-source.mustache

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ namespace {{this}} {
1010
{{#hasModelImport}}
1111
using namespace {{modelNamespace}};{{/hasModelImport}}
1212

13-
{{classname}}Impl::{{classname}}Impl(std::shared_ptr<Pistache::Rest::Router> rtr)
13+
{{classname}}Impl::{{classname}}Impl(const std::shared_ptr<Pistache::Rest::Router>& rtr)
1414
: {{classname}}(rtr)
15-
{ }
15+
{
16+
}
1617

1718
{{#operation}}
1819
{{#vendorExtensions.x-codegen-pistache-is-parsing-supported}}
@@ -31,4 +32,4 @@ void {{classname}}Impl::{{operationIdSnakeCase}}(const Pistache::Rest::Request &
3132
}
3233
{{/apiNamespaceDeclarations}}
3334

34-
{{/operations}}
35+
{{/operations}}

modules/openapi-generator/src/main/resources/cpp-pistache-server/api-source.mustache

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,18 @@
44
#include "{{classname}}.h"
55
#include "{{prefix}}Helpers.h"
66

7-
{{#apiNamespaceDeclarations}}
8-
namespace {{this}} {
9-
{{/apiNamespaceDeclarations}}
7+
namespace {{apiNamespace}}
8+
{
109
1110
using namespace {{helpersNamespace}};
1211
{{#hasModelImport}}
1312
using namespace {{modelNamespace}};{{/hasModelImport}}
1413

15-
{{classname}}::{{classname}}(std::shared_ptr<Pistache::Rest::Router> rtr) {
16-
router = rtr;
14+
const std::string {{classname}}::base = "{{basePathWithoutHost}}";
15+
16+
{{classname}}::{{classname}}(const std::shared_ptr<Pistache::Rest::Router>& rtr)
17+
: router(rtr)
18+
{
1719
}
1820

1921
void {{classname}}::init() {
@@ -31,8 +33,26 @@ void {{classname}}::setupRoutes() {
3133
router->addCustomHandler(Routes::bind(&{{classname}}::{{classnameSnakeLowerCase}}_default_handler, this));
3234
}
3335

36+
std::pair<Pistache::Http::Code, std::string> {{classname}}::handleParsingException(const std::exception& ex) const noexcept
37+
{
38+
try {
39+
throw ex;
40+
} catch (nlohmann::detail::exception &e) {
41+
return std::make_pair(Pistache::Http::Code::Bad_Request, e.what());
42+
} catch ({{helpersNamespace}}::ValidationException &e) {
43+
return std::make_pair(Pistache::Http::Code::Bad_Request, e.what());
44+
}
45+
}
46+
47+
std::pair<Pistache::Http::Code, std::string> {{classname}}::handleOperationException(const std::exception& ex) const noexcept
48+
{
49+
return std::make_pair(Pistache::Http::Code::Internal_Server_Error, ex.what());
50+
}
51+
3452
{{#operation}}
3553
void {{classname}}::{{operationIdSnakeCase}}_handler(const Pistache::Rest::Request &{{#hasParams}}request{{/hasParams}}, Pistache::Http::ResponseWriter response) {
54+
try {
55+
3656
{{#vendorExtensions.x-codegen-pistache-is-parsing-supported}}
3757
{{#hasPathParams}}
3858
// Getting the path params
@@ -71,41 +91,47 @@ void {{classname}}::{{operationIdSnakeCase}}_handler(const Pistache::Rest::Reque
7191
{{#hasBodyParam}}
7292
{{#bodyParam}}
7393
{{^isPrimitiveType}}
74-
nlohmann::json::parse(request.body()).get_to({{paramName}});
94+
nlohmann::json::parse(request.body()).get_to({{paramName}});
95+
{{paramName}}.validate();
7596
{{/isPrimitiveType}}
7697
{{#isPrimitiveType}}
77-
{{paramName}} = request.body();
98+
{{paramName}} = request.body();
7899
{{/isPrimitiveType}}
100+
} catch (std::exception &e) {
101+
const std::pair<Pistache::Http::Code, std::string> errorInfo = this->handleParsingException(e);
102+
response.send(errorInfo.first, errorInfo.second);
103+
return;
104+
}
105+
106+
try {
79107
{{/bodyParam}}
80108
{{/hasBodyParam}}
81-
this->{{operationIdSnakeCase}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}response);
109+
this->{{operationIdSnakeCase}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}response);
82110
{{/vendorExtensions.x-codegen-pistache-is-parsing-supported}}
83111
{{^vendorExtensions.x-codegen-pistache-is-parsing-supported}}
84112
try {
85113
this->{{operationIdSnakeCase}}(request, response);
86114
{{/vendorExtensions.x-codegen-pistache-is-parsing-supported}}
87-
} catch (nlohmann::detail::exception &e) {
88-
//send a 400 error
89-
response.send(Pistache::Http::Code::Bad_Request, e.what());
90-
return;
91115
} catch (Pistache::Http::HttpError &e) {
92116
response.send(static_cast<Pistache::Http::Code>(e.code()), e.what());
93117
return;
94118
} catch (std::exception &e) {
95-
//send a 500 error
96-
response.send(Pistache::Http::Code::Internal_Server_Error, e.what());
119+
const std::pair<Pistache::Http::Code, std::string> errorInfo = this->handleOperationException(e);
120+
response.send(errorInfo.first, errorInfo.second);
97121
return;
98122
}
99123

124+
} catch (std::exception &e) {
125+
response.send(Pistache::Http::Code::Internal_Server_Error, e.what());
126+
}
127+
100128
}
101129
{{/operation}}
102130

103131
void {{classname}}::{{classnameSnakeLowerCase}}_default_handler(const Pistache::Rest::Request &, Pistache::Http::ResponseWriter response) {
104132
response.send(Pistache::Http::Code::Not_Found, "The requested method does not exist");
105133
}
106134

107-
{{#apiNamespaceDeclarations}}
108-
}
109-
{{/apiNamespaceDeclarations}}
135+
} // namespace {{apiNamespace}}
110136

111137
{{/operations}}

modules/openapi-generator/src/main/resources/cpp-pistache-server/helpers-header.mustache

Lines changed: 74 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,80 @@
1414
#include <vector>
1515
#include <map>
1616

17-
{{#helpersNamespaceDeclarations}}
18-
namespace {{this}} {
19-
{{/helpersNamespaceDeclarations}}
17+
namespace {{helpersNamespace}}
18+
{
19+
20+
class ValidationException : public std::runtime_error
21+
{
22+
public:
23+
explicit ValidationException(const std::string& what)
24+
: std::runtime_error(what)
25+
{ }
26+
~ValidationException() override = default;
27+
};
28+
29+
/// <summary>
30+
/// Validate a string against the full-date definition of RFC 3339, section 5.6.
31+
/// </summary>
32+
bool validateRfc3339_date(const std::string& str);
33+
34+
/// <summary>
35+
/// Validate a string against the date-time definition of RFC 3339, section 5.6.
36+
/// </summary>
37+
bool validateRfc3339_date_time(const std::string& str);
38+
39+
namespace sfinae_helpers
40+
{
41+
struct NoType {};
42+
template <typename T1, typename T2> NoType operator==(const T1&, const T2&);
43+
44+
template <typename T1, typename T2> class EqualsOperatorAvailable
45+
{
46+
public:
47+
enum
48+
{
49+
value = !std::is_same< decltype(std::declval<T1>() == std::declval<T2>()), NoType >::value
50+
};
51+
};
52+
} // namespace sfinae_helpers
53+
54+
55+
/// <summary>
56+
/// Determine if the given vector<T> only has unique elements. T must provide the == operator.
57+
/// </summary>
58+
template <typename T>
59+
bool hasOnlyUniqueItems(const std::vector<T>& vec)
60+
{
61+
static_assert(sfinae_helpers::EqualsOperatorAvailable<T, T>::value,
62+
"hasOnlyUniqueItems<T> cannot be called, passed template type does not provide == operator.");
63+
if (vec.size() <= 1)
64+
{
65+
return true;
66+
}
67+
// Compare every element of vec to every other element of vec.
68+
// This isn't an elegant way to do this, since it's O(n^2),
69+
// but it's the best solution working only with the == operator.
70+
// This could be greatly improved if our models provided a valid hash
71+
// and/or the < operator
72+
for (size_t i = 0; i < vec.size() - 1; i++)
73+
{
74+
for (size_t j = i + 1; j < vec.size(); j++)
75+
{
76+
if (vec[i] == vec[j])
77+
{
78+
return false;
79+
}
80+
}
81+
}
82+
return true;
83+
}
2084

2185
std::string toStringValue(const std::string &value);
22-
std::string toStringValue(const int32_t &value);
23-
std::string toStringValue(const int64_t &value);
24-
std::string toStringValue(const bool &value);
25-
std::string toStringValue(const float &value);
26-
std::string toStringValue(const double &value);
86+
std::string toStringValue(const int32_t value);
87+
std::string toStringValue(const int64_t value);
88+
std::string toStringValue(const bool value);
89+
std::string toStringValue(const float value);
90+
std::string toStringValue(const double value);
2791

2892
bool fromStringValue(const std::string &inStr, std::string &value);
2993
bool fromStringValue(const std::string &inStr, int32_t &value);
@@ -57,8 +121,6 @@ namespace {{this}} {
57121
return fromStringValue(inStrings, value);
58122
}
59123

60-
{{#helpersNamespaceDeclarations}}
61-
}
62-
{{/helpersNamespaceDeclarations}}
124+
} // namespace {{helpersNamespace}}
63125

64-
#endif // {{prefix}}Helpers_H_
126+
#endif // {{prefix}}Helpers_H_

0 commit comments

Comments
 (0)