Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[C++][Pistache] Generate API generalization interface #15279

Merged
merged 11 commits into from
Apr 26, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

import java.io.File;
import java.util.*;
import java.util.function.Predicate;

import static org.openapitools.codegen.utils.StringUtils.underscore;

Expand Down Expand Up @@ -113,11 +114,7 @@ public CppPistacheServerCodegen() {
VARIABLE_NAME_FIRST_CHARACTER_UPPERCASE_DESC,
Boolean.toString(this.variableNameFirstCharacterUppercase));

supportingFiles.add(new SupportingFile("helpers-header.mustache", "model", modelNamePrefix + "Helpers.h"));
supportingFiles.add(new SupportingFile("helpers-source.mustache", "model", modelNamePrefix + "Helpers.cpp"));
supportingFiles.add(new SupportingFile("main-api-server.mustache", "", modelNamePrefix + "main-api-server.cpp"));
supportingFiles.add(new SupportingFile("cmake.mustache", "", "CMakeLists.txt"));
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
setupSupportingFiles();

languageSpecificPrimitives = new HashSet<>(
Arrays.asList("int", "char", "bool", "long", "float", "double", "int32_t", "int64_t"));
Expand Down Expand Up @@ -149,6 +146,16 @@ public CppPistacheServerCodegen() {
importMapping.put("nlohmann::json", "#include <nlohmann/json.hpp>");
}

private void setupSupportingFiles() {
supportingFiles.clear();
supportingFiles.add(new SupportingFile("api-base-header.mustache", "api", "ApiBase.h"));
supportingFiles.add(new SupportingFile("helpers-header.mustache", "model", modelNamePrefix + "Helpers.h"));
supportingFiles.add(new SupportingFile("helpers-source.mustache", "model", modelNamePrefix + "Helpers.cpp"));
supportingFiles.add(new SupportingFile("main-api-server.mustache", "", modelNamePrefix + "main-api-server.cpp"));
supportingFiles.add(new SupportingFile("cmake.mustache", "", "CMakeLists.txt"));
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
}

@Override
public void processOpts() {
super.processOpts();
Expand All @@ -157,12 +164,7 @@ public void processOpts() {
}
if (additionalProperties.containsKey("modelNamePrefix")) {
additionalProperties().put("prefix", modelNamePrefix);
supportingFiles.clear();
supportingFiles.add(new SupportingFile("helpers-header.mustache", "model", modelNamePrefix + "Helpers.h"));
supportingFiles.add(new SupportingFile("helpers-source.mustache", "model", modelNamePrefix + "Helpers.cpp"));
supportingFiles.add(new SupportingFile("main-api-server.mustache", "", modelNamePrefix + "main-api-server.cpp"));
supportingFiles.add(new SupportingFile("cmake.mustache", "", "CMakeLists.txt"));
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
setupSupportingFiles();
}
if (additionalProperties.containsKey(RESERVED_WORD_PREFIX_OPTION)) {
reservedWordPrefix = (String) additionalProperties.get(RESERVED_WORD_PREFIX_OPTION);
Expand Down Expand Up @@ -267,61 +269,67 @@ public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<Mo
operations.put("classnameSnakeLowerCase", underscore(classname).toLowerCase(Locale.ROOT));
List<CodegenOperation> operationList = operations.getOperation();
for (CodegenOperation op : operationList) {
boolean consumeJson = false;
boolean isParsingSupported = true;
if (op.bodyParam != null) {
if (op.bodyParam.vendorExtensions == null) {
op.bodyParam.vendorExtensions = new HashMap<>();
}
postProcessSingleOperation(operations, op);
}

boolean isStringOrDate = op.bodyParam.isString || op.bodyParam.isDate;
op.bodyParam.vendorExtensions.put("x-codegen-pistache-is-string-or-date", isStringOrDate);
}
if (op.consumes != null) {
for (Map<String, String> consume : op.consumes) {
if (consume.get("mediaType") != null && consume.get("mediaType").equals("application/json")) {
consumeJson = true;
}
}
return objs;
}

private void postProcessSingleOperation(OperationMap operations, CodegenOperation op) {
if (op.vendorExtensions == null) {
op.vendorExtensions = new HashMap<>();
}

if (op.bodyParam != null) {
if (op.bodyParam.vendorExtensions == null) {
op.bodyParam.vendorExtensions = new HashMap<>();
}

op.httpMethod = op.httpMethod.substring(0, 1).toUpperCase(Locale.ROOT) + op.httpMethod.substring(1).toLowerCase(Locale.ROOT);
boolean isStringOrDate = op.bodyParam.isString || op.bodyParam.isDate;
op.bodyParam.vendorExtensions.put("x-codegen-pistache-is-string-or-date", isStringOrDate);
}

for (CodegenParameter param : op.allParams) {
if (param.isFormParam) isParsingSupported = false;
if (param.isFile) isParsingSupported = false;
if (param.isCookieParam) isParsingSupported = false;
boolean consumeJson = false;
if (op.consumes != null) {
Predicate<Map<String,String>> isMediaTypeJson = consume -> (consume.get("mediaType") != null && consume.get("mediaType").equals("application/json"));
consumeJson = op.consumes.stream().anyMatch(isMediaTypeJson);
}
op.vendorExtensions.put("x-codegen-pistache-consumes-json", consumeJson);

//TODO: This changes the info about the real type but it is needed to parse the header params
if (param.isHeaderParam) {
param.dataType = "std::optional<Pistache::Http::Header::Raw>";
param.baseType = "std::optional<Pistache::Http::Header::Raw>";
} else if (param.isQueryParam) {
if (param.isPrimitiveType) {
param.dataType = "std::optional<" + param.dataType + ">";
} else {
param.dataType = "std::optional<" + param.dataType + ">";
param.baseType = "std::optional<" + param.baseType + ">";
}
}
}
op.httpMethod = op.httpMethod.substring(0, 1).toUpperCase(Locale.ROOT) + op.httpMethod.substring(1).toLowerCase(Locale.ROOT);

if (op.vendorExtensions == null) {
op.vendorExtensions = new HashMap<>();
}
op.vendorExtensions.put("x-codegen-pistache-consumes-json", consumeJson);
op.vendorExtensions.put("x-codegen-pistache-is-parsing-supported", isParsingSupported);
boolean isParsingSupported = true;
for (CodegenParameter param : op.allParams) {
boolean paramSupportsParsing = (!param.isFormParam && !param.isFile && !param.isCookieParam);
isParsingSupported = isParsingSupported && paramSupportsParsing;

// Check if any one of the operations needs a model, then at API file level, at least one model has to be included.
for (String hdr : op.imports) {
if (importMapping.containsKey(hdr)) {
continue;
}
operations.put("hasModelImport", true);
}
postProcessSingleParam(param);
}
op.vendorExtensions.put("x-codegen-pistache-is-parsing-supported", isParsingSupported);

return objs;
// Check if any one of the operations needs a model, then at API file level, at least one model has to be included.
Predicate<String> importNotInImportMapping = hdr -> !importMapping.containsKey(hdr);
if (op.imports.stream().anyMatch(importNotInImportMapping)) {
operations.put("hasModelImport", true);
}
}

/**
* postProcessSingleParam - Modifies a single parameter, adjusting generated
* data types for Header and Query parameters.
* @param param CodegenParameter to be modified.
*/
private static void postProcessSingleParam(CodegenParameter param) {
CTerasa-ep marked this conversation as resolved.
Show resolved Hide resolved
//TODO: This changes the info about the real type but it is needed to parse the header params
if (param.isHeaderParam) {
param.dataType = "std::optional<Pistache::Http::Header::Raw>";
param.baseType = "std::optional<Pistache::Http::Header::Raw>";
} else if (param.isQueryParam) {
param.dataType = "std::optional<" + param.dataType + ">";
if (!param.isPrimitiveType) {
param.baseType = "std::optional<" + param.baseType + ">";
}
}
}

@Override
Expand All @@ -334,17 +342,27 @@ public String apiFilename(String templateName, String tag) {
String result = super.apiFilename(templateName, tag);

if (templateName.endsWith("impl-header.mustache")) {
int ix = result.lastIndexOf(File.separatorChar);
result = result.substring(0, ix) + result.substring(ix, result.length() - 2) + "Impl.h";
result = result.replace(apiFileFolder(), implFileFolder());
result = implFilenameFromApiFilename(result, ".h");
} else if (templateName.endsWith("impl-source.mustache")) {
int ix = result.lastIndexOf(File.separatorChar);
result = result.substring(0, ix) + result.substring(ix, result.length() - 4) + "Impl.cpp";
result = result.replace(apiFileFolder(), implFileFolder());
result = implFilenameFromApiFilename(result, ".cpp");
}
return result;
}

/**
* implFilenameFromApiFilename - Inserts the string "Impl" in front of the
* suffix and replace "api" with "impl" directory prefix.
*
* @param filename Filename of the api-file to be modified
* @param suffix Suffix of the file (usually ".cpp" or ".h")
* @return a filename string of impl file.
*/
private String implFilenameFromApiFilename(String filename, String suffix) {
String result = filename.substring(0, filename.length() - suffix.length()) + "Impl" + suffix;
result = result.replace(apiFileFolder(), implFileFolder());
return result;
}

@Override
public String toApiFilename(String name) {
return toApiName(name);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{{>licenseInfo}}
/*
* ApiBase.h
*
* Generalization of the Api classes
*/

#ifndef ApiBase_H_
#define ApiBase_H_

#include <pistache/router.h>
#include <memory>

namespace {{apiNamespace}}
{

class ApiBase {
public:
explicit ApiBase(const std::shared_ptr<Pistache::Rest::Router>& rtr) : router(rtr) {};
virtual ~ApiBase() = default;
virtual void init() = 0;

protected:
const std::shared_ptr<Pistache::Rest::Router> router;
};

} // namespace {{apiNamespace}}

#endif /* ApiBase_H_ */
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#define {{classname}}_H_

{{{defaultInclude}}}
#include "ApiBase.h"

#include <pistache/http.h>
#include <pistache/router.h>
#include <pistache/http_headers.h>
Expand All @@ -22,7 +24,7 @@
namespace {{apiNamespace}}
{

class {{declspec}} {{classname}} {
class {{declspec}} {{classname}} : public ApiBase {
public:
explicit {{classname}}(const std::shared_ptr<Pistache::Rest::Router>& rtr);
virtual ~{{classname}}() = default;
Expand All @@ -38,8 +40,6 @@ private:
{{/operation}}
void {{classnameSnakeLowerCase}}_default_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response);

const std::shared_ptr<Pistache::Rest::Router> router;

/// <summary>
/// Helper function to handle unexpected Exceptions during Parameter parsing and validation.
/// May be overridden to return custom error formats. This is called inside a catch block.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ using namespace {{modelNamespace}};{{/hasModelImport}}
const std::string {{classname}}::base = "{{basePathWithoutHost}}";

{{classname}}::{{classname}}(const std::shared_ptr<Pistache::Rest::Router>& rtr)
: router(rtr)
: ApiBase(rtr)
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
#include <signal.h>
#include <unistd.h>
#endif

#include "ApiBase.h"
{{#apiInfo}}{{#apis}}{{#operations}}
#include "{{classname}}Impl.h"{{/operations}}{{/apis}}{{/apiInfo}}

Expand Down Expand Up @@ -66,13 +68,16 @@ int main() {
opts.maxResponseSize(PISTACHE_SERVER_MAX_RESPONSE_SIZE);
httpEndpoint->init(opts);

auto apiImpls = std::vector<std::shared_ptr<ApiBase>>();
{{#apiInfo}}{{#apis}}{{#operations}}
{{classname}}Impl {{classname}}server(router);
{{classname}}server.init();{{/operations}}{{/apis}}{{/apiInfo}}
apiImpls.push_back(std::make_shared<{{classname}}Impl>(router));{{/operations}}{{/apis}}{{/apiInfo}}

for (auto api : apiImpls) {
api->init();
}

httpEndpoint->setHandler(router->handler());
httpEndpoint->serve();

httpEndpoint->shutdown();

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
CMakeLists.txt
README.md
api/ApiBase.h
api/PetApi.cpp
api/PetApi.h
api/StoreApi.cpp
Expand Down
39 changes: 39 additions & 0 deletions samples/server/petstore/cpp-pistache/api/ApiBase.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* OpenAPI Petstore
* This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
/*
* ApiBase.h
*
* Generalization of the Api classes
*/

#ifndef ApiBase_H_
#define ApiBase_H_

#include <pistache/router.h>
#include <memory>

namespace org::openapitools::server::api
{

class ApiBase {
public:
explicit ApiBase(const std::shared_ptr<Pistache::Rest::Router>& rtr) : router(rtr) {};
virtual ~ApiBase() = default;
virtual void init() = 0;

protected:
const std::shared_ptr<Pistache::Rest::Router> router;
};

} // namespace org::openapitools::server::api

#endif /* ApiBase_H_ */
2 changes: 1 addition & 1 deletion samples/server/petstore/cpp-pistache/api/PetApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ using namespace org::openapitools::server::model;
const std::string PetApi::base = "/v2";

PetApi::PetApi(const std::shared_ptr<Pistache::Rest::Router>& rtr)
: router(rtr)
: ApiBase(rtr)
{
}

Expand Down
6 changes: 3 additions & 3 deletions samples/server/petstore/cpp-pistache/api/PetApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#define PetApi_H_


#include "ApiBase.h"

#include <pistache/http.h>
#include <pistache/router.h>
#include <pistache/http_headers.h>
Expand All @@ -33,7 +35,7 @@
namespace org::openapitools::server::api
{

class PetApi {
class PetApi : public ApiBase {
public:
explicit PetApi(const std::shared_ptr<Pistache::Rest::Router>& rtr);
virtual ~PetApi() = default;
Expand All @@ -54,8 +56,6 @@ class PetApi {
void upload_file_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response);
void pet_api_default_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response);

const std::shared_ptr<Pistache::Rest::Router> router;

/// <summary>
/// Helper function to handle unexpected Exceptions during Parameter parsing and validation.
/// May be overridden to return custom error formats. This is called inside a catch block.
Expand Down
2 changes: 1 addition & 1 deletion samples/server/petstore/cpp-pistache/api/StoreApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ using namespace org::openapitools::server::model;
const std::string StoreApi::base = "/v2";

StoreApi::StoreApi(const std::shared_ptr<Pistache::Rest::Router>& rtr)
: router(rtr)
: ApiBase(rtr)
{
}

Expand Down
Loading