Add C++ UE4 client generator (#6399)

* Added new language: UE4 C++ client

* rename generator

* add copyright

* update doc

* fix with Locale.ROOT

* add new file

* minor improvements

* remove postProcessModels

Co-authored-by: Samuel Kahn <samuel@kahncode.com>
This commit is contained in:
William Cheng
2020-05-25 18:33:48 +08:00
committed by GitHub
parent 6be3bc0f8a
commit c000eaef73
54 changed files with 6607 additions and 1 deletions

View File

@@ -0,0 +1,544 @@
/*
* Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
*
* 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
*
* https://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.
*/
package org.openapitools.codegen.languages;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Schema;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.CodegenType;
import org.openapitools.codegen.SupportingFile;
import org.openapitools.codegen.meta.GeneratorMetadata;
import org.openapitools.codegen.meta.Stability;
import org.openapitools.codegen.utils.ModelUtils;
import java.io.File;
import java.util.*;
import static org.openapitools.codegen.utils.StringUtils.camelize;
public class CppUE4ClientCodegen extends AbstractCppCodegen {
public static final String CPP_NAMESPACE = "cppNamespace";
public static final String CPP_NAMESPACE_DESC = "C++ namespace (convention: name::space::for::api).";
public static final String UNREAL_MODULE_NAME = "unrealModuleName";
public static final String UNREAL_MODULE_NAME_DESC = "Name of the generated unreal module (optional)";
public static final String OPTIONAL_PROJECT_FILE_DESC = "Generate Build.cs";
protected String unrealModuleName = "OpenAPI";
// Will be treated as pointer
protected Set<String> pointerClasses = new HashSet<String>();
// source folder where to write the files
protected String privateFolder = "Private";
protected String publicFolder = "Public";
protected String apiVersion = "1.0.0";
protected Map<String, String> namespaces = new HashMap<String, String>();
// Will be included using the <> syntax, not used in Unreal's coding convention
protected Set<String> systemIncludes = new HashSet<String>();
protected String cppNamespace = unrealModuleName;
protected boolean optionalProjectFileFlag = true;
public CppUE4ClientCodegen() {
super();
generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
.stability(Stability.BETA)
.build();
// set the output folder here
outputFolder = "generated-code/cpp-ue4";
// set modelNamePrefix as default for cpp-ue4
if ("".equals(modelNamePrefix)) {
modelNamePrefix = unrealModuleName;
}
/*
* Models. You can write model files using the modelTemplateFiles map.
* if you want to create one template for file, you can do so here.
* for multiple files for model, just put another entry in the `modelTemplateFiles` with
* a different extension
*/
modelTemplateFiles.put(
"model-header.mustache",
".h");
modelTemplateFiles.put(
"model-source.mustache",
".cpp");
/*
* Api classes. You can write classes for each Api file with the apiTemplateFiles map.
* as with models, add multiple entries with different extensions for multiple files per
* class
*/
apiTemplateFiles.put(
"api-header.mustache", // the template to use
".h"); // the extension for each file to write
apiTemplateFiles.put(
"api-source.mustache", // the template to use
".cpp"); // the extension for each file to write
apiTemplateFiles.put(
"api-operations-header.mustache", // the template to use
".h"); // the extension for each file to write
apiTemplateFiles.put(
"api-operations-source.mustache", // the template to use
".cpp"); // the extension for each file to write
/*
* Template Location. This is the location which templates will be read from. The generator
* will use the resource stream to attempt to read the templates.
*/
embeddedTemplateDir = templateDir = "cpp-ue4";
// CLI options
addOption(CPP_NAMESPACE, CPP_NAMESPACE_DESC, this.cppNamespace);
addOption(UNREAL_MODULE_NAME, UNREAL_MODULE_NAME_DESC, this.unrealModuleName);
addSwitch(CodegenConstants.OPTIONAL_PROJECT_FILE, OPTIONAL_PROJECT_FILE_DESC, this.optionalProjectFileFlag);
/*
* Additional Properties. These values can be passed to the templates and
* are available in models, apis, and supporting files
*/
additionalProperties.put("apiVersion", apiVersion);
additionalProperties().put("modelNamePrefix", modelNamePrefix);
additionalProperties().put("modelPackage", modelPackage);
additionalProperties().put("apiPackage", apiPackage);
additionalProperties().put("dllapi", unrealModuleName.toUpperCase(Locale.ROOT) + "_API");
additionalProperties().put("unrealModuleName", unrealModuleName);
// Write defaults namespace in properties so that it can be accessible in templates.
// At this point command line has not been parsed so if value is given
// in command line it will superseed this content
additionalProperties.put("cppNamespace", cppNamespace);
additionalProperties.put("unrealModuleName", unrealModuleName);
/*
* Language Specific Primitives. These types will not trigger imports by
* the client generator
*/
languageSpecificPrimitives = new HashSet<String>(
Arrays.asList(
"bool",
"int32",
"int64",
"float",
"double",
"FString",
"FDateTime",
"FGuid",
"TArray",
"TArray<uint8>", // For byte arrays
"TMap",
"TSharedPtr<FJsonObject>")
);
supportingFiles.add(new SupportingFile("model-base-header.mustache", publicFolder, modelNamePrefix + "BaseModel.h"));
supportingFiles.add(new SupportingFile("model-base-source.mustache", privateFolder, modelNamePrefix + "BaseModel.cpp"));
supportingFiles.add(new SupportingFile("helpers-header.mustache", publicFolder, modelNamePrefix + "Helpers.h"));
supportingFiles.add(new SupportingFile("helpers-source.mustache", privateFolder, modelNamePrefix + "Helpers.cpp"));
if (optionalProjectFileFlag) {
supportingFiles.add(new SupportingFile("Build.cs.mustache", unrealModuleName + ".Build.cs"));
supportingFiles.add(new SupportingFile("module-header.mustache", privateFolder, unrealModuleName + "Module.h"));
supportingFiles.add(new SupportingFile("module-source.mustache", privateFolder, unrealModuleName + "Module.cpp"));
}
super.typeMapping = new HashMap<String, String>();
// Maps C++ types during call to getSchemaType, see DefaultCodegen.getSchemaType and not the types/formats
// defined in openapi specification "array" is also used explicitly in the generator for containers
typeMapping.clear();
typeMapping.put("integer", "int32");
typeMapping.put("long", "int64");
typeMapping.put("float", "float");
typeMapping.put("number", "double");
typeMapping.put("double", "double");
typeMapping.put("string", "FString");
typeMapping.put("byte", "uint8");
typeMapping.put("binary", "TArray<uint8>");
typeMapping.put("ByteArray", "TArray<uint8>");
typeMapping.put("password", "FString");
typeMapping.put("boolean", "bool");
typeMapping.put("date", "FDateTime");
typeMapping.put("Date", "FDateTime");
typeMapping.put("date-time", "FDateTime");
typeMapping.put("DateTime", "FDateTime");
typeMapping.put("array", "TArray");
typeMapping.put("list", "TArray");
typeMapping.put("map", "TMap");
typeMapping.put("object", "TSharedPtr<FJsonObject>");
typeMapping.put("Object", "TSharedPtr<FJsonObject>");
typeMapping.put("file", "HttpFileInput");
typeMapping.put("UUID", "FGuid");
importMapping = new HashMap<String, String>();
importMapping.put("HttpFileInput", "#include \"" + modelNamePrefix + "Helpers.h\"");
namespaces = new HashMap<String, String>();
}
@Override
public void processOpts() {
super.processOpts();
if (additionalProperties.containsKey("cppNamespace")) {
cppNamespace = (String) additionalProperties.get("cppNamespace");
}
additionalProperties.put("cppNamespaceDeclarations", cppNamespace.split("\\::"));
boolean updateSupportingFiles = false;
if (additionalProperties.containsKey("unrealModuleName")) {
unrealModuleName = (String) additionalProperties.get("unrealModuleName");
additionalProperties().put("dllapi", unrealModuleName.toUpperCase(Locale.ROOT) + "_API");
modelNamePrefix = unrealModuleName;
updateSupportingFiles = true;
}
if (additionalProperties.containsKey("modelNamePrefix")) {
modelNamePrefix = (String) additionalProperties.get("modelNamePrefix");
updateSupportingFiles = true;
}
if (additionalProperties.containsKey(CodegenConstants.OPTIONAL_PROJECT_FILE)) {
setOptionalProjectFileFlag(convertPropertyToBooleanAndWriteBack(CodegenConstants.OPTIONAL_PROJECT_FILE));
} else {
additionalProperties.put(CodegenConstants.OPTIONAL_PROJECT_FILE, optionalProjectFileFlag);
}
if (updateSupportingFiles) {
supportingFiles.clear();
supportingFiles.add(new SupportingFile("model-base-header.mustache", publicFolder, modelNamePrefix + "BaseModel.h"));
supportingFiles.add(new SupportingFile("model-base-source.mustache", privateFolder, modelNamePrefix + "BaseModel.cpp"));
supportingFiles.add(new SupportingFile("helpers-header.mustache", publicFolder, modelNamePrefix + "Helpers.h"));
supportingFiles.add(new SupportingFile("helpers-source.mustache", privateFolder, modelNamePrefix + "Helpers.cpp"));
if (optionalProjectFileFlag) {
supportingFiles.add(new SupportingFile("Build.cs.mustache", unrealModuleName + ".Build.cs"));
supportingFiles.add(new SupportingFile("module-header.mustache", privateFolder, unrealModuleName + "Module.h"));
supportingFiles.add(new SupportingFile("module-source.mustache", privateFolder, unrealModuleName + "Module.cpp"));
}
importMapping.put("HttpFileInput", "#include \"" + modelNamePrefix + "Helpers.h\"");
}
}
public void setOptionalProjectFileFlag(boolean flag) {
this.optionalProjectFileFlag = flag;
}
/**
* Configures the type of generator.
*
* @return the CodegenType for this generator
* @see org.openapitools.codegen.CodegenType
*/
@Override
public CodegenType getTag() {
return CodegenType.CLIENT;
}
/**
* Configures a friendly name for the generator. This will be used by the generator
* to select the library with the -l flag.
*
* @return the friendly name for the generator
*/
@Override
public String getName() {
return "cpp-ue4";
}
/**
* Returns human-friendly help for the generator. Provide the consumer with help
* tips, parameters here
*
* @return A string value for the help message
*/
@Override
public String getHelp() {
return "Generates a Unreal Engine 4 C++ Module (beta).";
}
@Override
public String toModelImport(String name) {
if (namespaces.containsKey(name)) {
return "using " + namespaces.get(name) + ";";
} else if (systemIncludes.contains(name)) {
return "#include <" + name + ">";
}
String folder = modelPackage().replace("::", File.separator);
if (!folder.isEmpty())
folder += File.separator;
return "#include \"" + folder + name + ".h\"";
}
@Override
protected boolean needToImport(String type) {
boolean shouldImport = super.needToImport(type);
if (shouldImport)
return !languageSpecificPrimitives.contains(type);
else
return false;
}
/**
* Escapes a reserved word as defined in the `reservedWords` array. Handle escaping
* those terms here. This logic is only called if a variable matches the reserved words
*
* @return the escaped term
*/
@Override
public String escapeReservedWord(String name) {
if (this.reservedWordsMappings().containsKey(name)) {
return this.reservedWordsMappings().get(name);
}
return "_" + name;
}
/**
* Location to write model files. You can use the modelPackage() as defined when the class is
* instantiated
*/
@Override
public String modelFileFolder() {
return outputFolder + File.separator + modelPackage().replace("::", File.separator);
}
/**
* Location to write api files. You can use the apiPackage() as defined when the class is
* instantiated
*/
@Override
public String apiFileFolder() {
return outputFolder + File.separator + apiPackage().replace("::", File.separator);
}
/*
@Override
public String modelFilename(String templateName, String tag) {
String suffix = modelTemplateFiles().get(templateName);
String folder = privateFolder;
if (suffix == ".h") {
folder = publicFolder;
}
return modelFileFolder() + File.separator + folder + File.separator + toModelFilename(tag) + suffix;
}
*/
@Override
public String toModelFilename(String name) {
name = sanitizeName(name);
return modelNamePrefix + camelize(name);
}
@Override
public String apiFilename(String templateName, String tag) {
String suffix = apiTemplateFiles().get(templateName);
String folder = privateFolder;
if (".h".equals(suffix)) {
folder = publicFolder;
}
if (templateName.startsWith("api-operations")) {
return apiFileFolder() + File.separator + folder + File.separator + toApiFilename(tag) + "Operations" + suffix;
} else {
return apiFileFolder() + File.separator + folder + File.separator + toApiFilename(tag) + suffix;
}
}
@Override
public String toApiFilename(String name) {
name = sanitizeName(name);
return modelNamePrefix + camelize(name) + "Api";
}
/**
* Optional - type declaration. This is a String which is used by the templates to instantiate your
* types. There is typically special handling for different property types
*
* @return a string value used as the `dataType` field for model templates, `returnType` for api templates
*/
@Override
public String getTypeDeclaration(Schema p) {
String openAPIType = getSchemaType(p);
if (ModelUtils.isArraySchema(p)) {
ArraySchema ap = (ArraySchema) p;
String inner = getSchemaType(ap.getItems());
return getSchemaType(p) + "<" + getTypeDeclaration(inner) + ">";
} else if (ModelUtils.isMapSchema(p)) {
String inner = getSchemaType(ModelUtils.getAdditionalProperties(p));
return getSchemaType(p) + "<FString, " + getTypeDeclaration(inner) + ">";
}
if (pointerClasses.contains(openAPIType)) {
return openAPIType + "*";
} else if (languageSpecificPrimitives.contains(openAPIType)) {
return toModelName(openAPIType);
} else {
return openAPIType;
}
}
@Override
public String toDefaultValue(Schema p) {
if (ModelUtils.isStringSchema(p)) {
if (p.getDefault() != null) {
return "TEXT(\"" + p.getDefault().toString() + "\")";
} else {
return null;
}
} else if (ModelUtils.isBooleanSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
} else {
return "false";
}
} else if (ModelUtils.isDateSchema(p)) {
return "FDateTime(0)";
} else if (ModelUtils.isDateTimeSchema(p)) {
return "FDateTime(0)";
} else if (ModelUtils.isDoubleSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
} else {
return "0.0";
}
} else if (ModelUtils.isFloatSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
} else {
return "0.0f";
}
} else if (ModelUtils.isIntegerSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
} else {
return "0";
}
} else if (ModelUtils.isLongSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
} else {
return "0";
}
}
return null;
}
/**
* Optional - OpenAPI type conversion. This is used to map OpenAPI types in a `Property` into
* either language specific types via `typeMapping` or into complex models if there is not a mapping.
*
* @return a string value of the type or complex model for this property
* @see io.swagger.v3.oas.models.media.Schema
*/
@Override
public String getSchemaType(Schema p) {
String openAPIType = super.getSchemaType(p);
String type = null;
if (typeMapping.containsKey(openAPIType)) {
type = typeMapping.get(openAPIType);
if (languageSpecificPrimitives.contains(type)) {
return toModelName(type);
}
if (pointerClasses.contains(type)) {
return type;
}
} else {
type = openAPIType;
}
return toModelName(type);
}
@Override
public String toModelName(String type) {
if (typeMapping.keySet().contains(type) ||
typeMapping.values().contains(type) ||
importMapping.values().contains(type) ||
defaultIncludes.contains(type) ||
languageSpecificPrimitives.contains(type)) {
return type;
} else {
return modelNamePrefix + camelize(sanitizeName(type), false);
}
}
@Override
public String toVarName(String name) {
// sanitize name
name = sanitizeName(name); // FIXME: a parameter should not be assigned. Also declare the methods parameters as 'final'.
// if it's all uppper case, convert to lower case
if (name.matches("^[A-Z_]*$")) {
name = name.toLowerCase(Locale.ROOT);
}
// for reserved word or word starting with number, append _
if (isReservedWord(name) || name.matches("^\\d.*")) {
name = escapeReservedWord(name);
}
//Unreal variable names are CamelCase
return camelize(name, false);
}
@Override
public String toEnumVarName(String name, String datatype) {
return toVarName(name);
}
@Override
public String toParamName(String name) {
return toVarName(name);
}
@Override
public String toApiName(String type) {
return modelNamePrefix + camelize(type, false) + "Api";
}
@Override
public String escapeQuotationMark(String input) {
// remove " to avoid code injection
return input.replace("\"", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
return input.replace("*/", "*_/").replace("/*", "/_*");
}
public String toBooleanGetter(String name) {
return "Is" + getterAndSetterCapitalize(name);
}
public String toGetter(String name) {
return "Get" + getterAndSetterCapitalize(name);
}
public String toSetter(String name) {
return "Set" + getterAndSetterCapitalize(name);
}
}

View File

@@ -16,6 +16,7 @@ org.openapitools.codegen.languages.CppPistacheServerCodegen
org.openapitools.codegen.languages.CppRestbedServerCodegen
org.openapitools.codegen.languages.CppRestSdkClientCodegen
org.openapitools.codegen.languages.CppTizenClientCodegen
org.openapitools.codegen.languages.CppUE4ClientCodegen
org.openapitools.codegen.languages.CSharpClientCodegen
org.openapitools.codegen.languages.CSharpNetCoreClientCodegen
org.openapitools.codegen.languages.CSharpDotNet2ClientCodegen
@@ -124,4 +125,4 @@ org.openapitools.codegen.languages.TypeScriptInversifyClientCodegen
org.openapitools.codegen.languages.TypeScriptJqueryClientCodegen
org.openapitools.codegen.languages.TypeScriptNodeClientCodegen
org.openapitools.codegen.languages.TypeScriptReduxQueryClientCodegen
org.openapitools.codegen.languages.TypeScriptRxjsClientCodegen
org.openapitools.codegen.languages.TypeScriptRxjsClientCodegen

View File

@@ -0,0 +1,19 @@
{{>licenseInfo}}
using System;
using System.IO;
using UnrealBuildTool;
public class {{unrealModuleName}} : ModuleRules
{
public {{unrealModuleName}}(ReadOnlyTargetRules Target) : base(Target)
{
PublicDependencyModuleNames.AddRange(
new string[]
{
"Core",
"Http",
"Json",
}
);
}
}

View File

@@ -0,0 +1,42 @@
{{>licenseInfo}}
#pragma once
#include "CoreMinimal.h"
#include "{{modelNamePrefix}}BaseModel.h"
{{#cppNamespaceDeclarations}}
namespace {{this}}
{
{{/cppNamespaceDeclarations}}
class {{dllapi}} {{classname}}
{
public:
{{classname}}();
~{{classname}}();
void SetURL(const FString& Url);
void AddHeaderParam(const FString& Key, const FString& Value);
void ClearHeaderParams();
{{#operations}}{{#operation}}class {{operationIdCamelCase}}Request;
class {{operationIdCamelCase}}Response;
{{/operation}}{{/operations}}
{{#operations}}{{#operation}}DECLARE_DELEGATE_OneParam(F{{operationIdCamelCase}}Delegate, const {{operationIdCamelCase}}Response&);
{{/operation}}{{/operations}}
{{#operations}}{{#operation}}{{#description}}/* {{{description}}} */
{{/description}}bool {{operationIdCamelCase}}(const {{operationIdCamelCase}}Request& Request, const F{{operationIdCamelCase}}Delegate& Delegate = F{{operationIdCamelCase}}Delegate()) const;
{{/operation}}{{/operations}}
private:
{{#operations}}{{#operation}}void On{{operationIdCamelCase}}Response(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded, F{{operationIdCamelCase}}Delegate Delegate) const;
{{/operation}}{{/operations}}
bool IsValid() const;
void HandleResponse(FHttpResponsePtr HttpResponse, bool bSucceeded, Response& InOutResponse) const;
FString Url;
TMap<FString,FString> AdditionalHeaderParams;
};
{{#cppNamespaceDeclarations}}
}
{{/cppNamespaceDeclarations}}

View File

@@ -0,0 +1,64 @@
{{>licenseInfo}}
#pragma once
#include "{{modelNamePrefix}}BaseModel.h"
#include "{{classname}}.h"
{{#imports}}{{{import}}}
{{/imports}}
{{#cppNamespaceDeclarations}}
namespace {{this}}
{
{{/cppNamespaceDeclarations}}
{{#operations}}
{{#operation}}
/* {{summary}}
{{#notes}} *
* {{notes}}{{/notes}}
*/
class {{dllapi}} {{classname}}::{{operationIdCamelCase}}Request : public Request
{
public:
virtual ~{{operationIdCamelCase}}Request() {}
void SetupHttpRequest(const TSharedRef<IHttpRequest>& HttpRequest) const final;
FString ComputePath() const final;
{{#allParams}}
{{#isEnum}}
{{#allowableValues}}
enum class {{{enumName}}}
{
{{#enumVars}}
{{name}},
{{/enumVars}}
};
{{/allowableValues}}
{{#description}}/* {{{description}}} */
{{/description}}{{^required}}TOptional<{{/required}}{{{datatypeWithEnum}}}{{^required}}>{{/required}} {{paramName}}{{#required}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}{{/required}};
{{/isEnum}}
{{^isEnum}}
{{#description}}/* {{{description}}} */
{{/description}}{{^required}}TOptional<{{/required}}{{{dataType}}}{{^required}}>{{/required}} {{paramName}}{{#required}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}{{/required}};
{{/isEnum}}
{{/allParams}}
};
class {{dllapi}} {{classname}}::{{operationIdCamelCase}}Response : public Response
{
public:
virtual ~{{operationIdCamelCase}}Response() {}
{{#responses.0}}
void SetHttpResponseCode(EHttpResponseCodes::Type InHttpResponseCode) final;
{{/responses.0}}
bool FromJson(const TSharedPtr<FJsonValue>& JsonObject) final;
{{#returnType}}{{{returnType}}} Content;{{/returnType}}
};
{{/operation}}
{{/operations}}
{{#cppNamespaceDeclarations}}
}
{{/cppNamespaceDeclarations}}

View File

@@ -0,0 +1,286 @@
{{>licenseInfo}}
#include "{{classname}}Operations.h"
#include "{{unrealModuleName}}Module.h"
#include "{{modelNamePrefix}}Helpers.h"
#include "Dom/JsonObject.h"
#include "Templates/SharedPointer.h"
#include "HttpModule.h"
#include "PlatformHttp.h"
{{#cppNamespaceDeclarations}}
namespace {{this}}
{
{{/cppNamespaceDeclarations}}
{{#operations}}{{#operation}}
{{#allParams}}
{{#isEnum}}
inline FString ToString(const {{classname}}::{{operationIdCamelCase}}Request::{{{enumName}}}& Value)
{
{{#allowableValues}}
switch (Value)
{
{{#enumVars}}
case {{classname}}::{{operationIdCamelCase}}Request::{{{enumName}}}::{{name}}:
return TEXT({{{value}}});
{{/enumVars}}
}
{{/allowableValues}}
UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Invalid {{classname}}::{{operationIdCamelCase}}Request::{{{enumName}}} Value (%d)"), (int)Value);
return TEXT("");
}
inline FStringFormatArg ToStringFormatArg(const {{classname}}::{{operationIdCamelCase}}Request::{{{enumName}}}& Value)
{
return FStringFormatArg(ToString(Value));
}
inline void WriteJsonValue(JsonWriter& Writer, const {{classname}}::{{operationIdCamelCase}}Request::{{{enumName}}}& Value)
{
WriteJsonValue(Writer, ToString(Value));
}
inline bool TryGetJsonValue(const TSharedPtr<FJsonValue>& JsonValue, {{classname}}::{{operationIdCamelCase}}Request::{{{enumName}}}& Value)
{
{{#allowableValues}}
FString TmpValue;
if (JsonValue->TryGetString(TmpValue))
{
static TMap<FString, {{classname}}::{{operationIdCamelCase}}Request::{{{enumName}}}> StringToEnum = { {{#enumVars}}
{ TEXT({{{value}}}), {{classname}}::{{operationIdCamelCase}}Request::{{{enumName}}}::{{name}} },{{/enumVars}} };
const auto Found = StringToEnum.Find(TmpValue);
if(Found)
{
Value = *Found;
return true;
}
}
{{/allowableValues}}
return false;
}
{{/isEnum}}
{{/allParams}}
FString {{classname}}::{{operationIdCamelCase}}Request::ComputePath() const
{
{{^pathParams.0}}
FString Path(TEXT("{{{path}}}"));
{{/pathParams.0}}
{{#pathParams.0}}
TMap<FString, FStringFormatArg> PathParams = { {{#pathParams}}
{ TEXT("{{baseName}}"), ToStringFormatArg({{paramName}}) }{{#hasMore}},{{/hasMore}}{{/pathParams}} };
FString Path = FString::Format(TEXT("{{{path}}}"), PathParams);
{{/pathParams.0}}
{{#queryParams.0}}
TArray<FString> QueryParams;
{{#queryParams}}
{{#required}}
{{^collectionFormat}}
QueryParams.Add(FString(TEXT("{{baseName}}=")) + ToUrlString({{paramName}}));
{{/collectionFormat}}
{{#collectionFormat}}
QueryParams.Add(FString(TEXT("{{baseName}}=")) + CollectionToUrlString_{{collectionFormat}}({{paramName}}, TEXT("{{baseName}}")));
{{/collectionFormat}}
{{/required}}
{{^required}}
{{^collectionFormat}}
if({{paramName}}.IsSet())
{
QueryParams.Add(FString(TEXT("{{baseName}}=")) + ToUrlString({{paramName}}.GetValue()));
}
{{/collectionFormat}}
{{#collectionFormat}}
if({{paramName}}.IsSet())
{
QueryParams.Add(FString(TEXT("{{baseName}}=")) + CollectionToUrlString_{{collectionFormat}}({{paramName}}.GetValue(), TEXT("{{baseName}}")));
}
{{/collectionFormat}}
{{/required}}
{{/queryParams}}
Path += TCHAR('?');
Path += FString::Join(QueryParams, TEXT("&"));
{{/queryParams.0}}
return Path;
}
void {{classname}}::{{operationIdCamelCase}}Request::SetupHttpRequest(const TSharedRef<IHttpRequest>& HttpRequest) const
{
static const TArray<FString> Consumes = { {{#consumes}}TEXT("{{{mediaType}}}"){{#hasMore}}, {{/hasMore}}{{/consumes}} };
//static const TArray<FString> Produces = { {{#produces}}TEXT("{{{mediaType}}}"){{#hasMore}}, {{/hasMore}}{{/produces}} };
HttpRequest->SetVerb(TEXT("{{httpMethod}}"));
{{#headerParams.0}}
// Header parameters
{{#headerParams}}
{{#required}}
HttpRequest->SetHeader(TEXT("{{baseName}}"), {{paramName}});
{{/required}}
{{^required}}
if ({{paramName}}.IsSet())
{
HttpRequest->SetHeader(TEXT("{{baseName}}"), {{paramName}}.GetValue());
}
{{/required}}
{{/headerParams}}
{{/headerParams.0}}
// Default to Json Body request
if (Consumes.Num() == 0 || Consumes.Contains(TEXT("application/json")))
{
{{#bodyParams.0}}
// Body parameters
FString JsonBody;
JsonWriter Writer = TJsonWriterFactory<>::Create(&JsonBody);
Writer->WriteObjectStart();
{{#bodyParams}}
{{#required}}
Writer->WriteIdentifierPrefix(TEXT("{{baseName}}")); WriteJsonValue(Writer, {{paramName}});
{{/required}}
{{^required}}
if ({{paramName}}.IsSet())
{
Writer->WriteIdentifierPrefix(TEXT("{{baseName}}")); WriteJsonValue(Writer, {{paramName}}.GetValue());
}
{{/required}}
{{/bodyParams}}
Writer->WriteObjectEnd();
Writer->Close();
HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json; charset=utf-8"));
HttpRequest->SetContentAsString(JsonBody);
{{/bodyParams.0}}
{{#formParams.0}}
{{#formParams}}
UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Form parameter ({{baseName}}) was ignored, cannot be used in JsonBody"));
{{/formParams}}
{{/formParams.0}}
}
else if (Consumes.Contains(TEXT("multipart/form-data")))
{
{{#formParams.0}}
HttpMultipartFormData FormData;
{{#formParams}}
{{#isContainer}}
UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Form parameter ({{baseName}}) was ignored, Collections are not supported in multipart form"));
{{/isContainer}}
{{^isContainer}}
{{#required}}
{{#isFile}}
FormData.AddFilePart(TEXT("{{baseName}}"), {{paramName}});
{{/isFile}}
{{#isBinary}}
FormData.AddBinaryPart(TEXT("{{baseName}}"), {{paramName}});
{{/isBinary}}
{{#isBinary}}
{{^isFile}}
FormData.AddStringPart(TEXT("{{baseName}}"), *ToUrlString({{paramName}}));
{{/isFile}}
{{/isBinary}}
{{/required}}
{{^required}}
if({{paramName}}.IsSet())
{
{{#isFile}}
FormData.AddFilePart(TEXT("{{baseName}}"), {{paramName}}.GetValue());
{{/isFile}}
{{#isBinary}}
FormData.AddBinaryPart(TEXT("{{baseName}}"), {{paramName}}.GetValue());
{{/isBinary}}
{{^isBinary}}
{{^isFile}}
FormData.AddStringPart(TEXT("{{baseName}}"), *ToUrlString({{paramName}}.GetValue()));
{{/isFile}}
{{/isBinary}}
}
{{/required}}
{{/isContainer}}
{{/formParams}}
FormData.SetupHttpRequest(HttpRequest);
{{/formParams.0}}
{{#bodyParams.0}}
{{#bodyParams}}
UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Body parameter ({{baseName}}) was ignored, not supported in multipart form"));
{{/bodyParams}}
{{/bodyParams.0}}
}
else if (Consumes.Contains(TEXT("application/x-www-form-urlencoded")))
{
{{#formParams.0}}
TArray<FString> FormParams;
{{#formParams}}
{{#isContainer}}
UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Form parameter ({{baseName}}) was ignored, Collections are not supported in urlencoded requests"));
{{/isContainer}}
{{#isFile}}
UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Form parameter ({{baseName}}) was ignored, Files are not supported in urlencoded requests"));
{{/isFile}}
{{^isFile}}
{{^isContainer}}
{{#required}}
FormParams.Add(FString(TEXT("{{baseName}}=")) + ToUrlString({{paramName}}));
{{/required}}
{{^required}}
if({{paramName}}.IsSet())
{
FormParams.Add(FString(TEXT("{{baseName}}=")) + ToUrlString({{paramName}}.GetValue()));
}
{{/required}}
{{/isContainer}}
{{/isFile}}
{{/formParams}}
HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/x-www-form-urlencoded; charset=utf-8"));
HttpRequest->SetContentAsString(FString::Join(FormParams, TEXT("&")));
{{/formParams.0}}
{{#bodyParams.0}}
{{#bodyParams}}
UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Body parameter ({{baseName}}) was ignored, not supported in urlencoded requests"));
{{/bodyParams}}
{{/bodyParams.0}}
}
else
{
UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Request ContentType not supported (%s)"), *FString::Join(Consumes, TEXT(",")));
}
}
{{#responses.0}}
void {{classname}}::{{operationIdCamelCase}}Response::SetHttpResponseCode(EHttpResponseCodes::Type InHttpResponseCode)
{
Response::SetHttpResponseCode(InHttpResponseCode);
switch ((int)InHttpResponseCode)
{
{{#responses}}
case {{code}}:
{{#isDefault}}
default:
{{/isDefault}}
SetResponseString(TEXT("{{message}}"));
break;
{{/responses}}
}
}
{{/responses.0}}
bool {{classname}}::{{operationIdCamelCase}}Response::FromJson(const TSharedPtr<FJsonValue>& JsonValue)
{
{{#returnType}}
return TryGetJsonValue(JsonValue, Content);
{{/returnType}}
{{^returnType}}
return true;
{{/returnType}}
}
{{/operation}}{{/operations}}
{{#cppNamespaceDeclarations}}
}
{{/cppNamespaceDeclarations}}

View File

@@ -0,0 +1,120 @@
{{>licenseInfo}}
#include "{{classname}}.h"
#include "{{classname}}Operations.h"
#include "{{unrealModuleName}}Module.h"
#include "HttpModule.h"
#include "Serialization/JsonSerializer.h"
{{#cppNamespaceDeclarations}}
namespace {{this}}
{
{{/cppNamespaceDeclarations}}
{{classname}}::{{classname}}()
: Url(TEXT("{{basePath}}"))
{
}
{{classname}}::~{{classname}}() {}
void {{classname}}::SetURL(const FString& InUrl)
{
Url = InUrl;
}
void {{classname}}::AddHeaderParam(const FString& Key, const FString& Value)
{
AdditionalHeaderParams.Add(Key, Value);
}
void {{classname}}::ClearHeaderParams()
{
AdditionalHeaderParams.Reset();
}
bool {{classname}}::IsValid() const
{
if (Url.IsEmpty())
{
UE_LOG(Log{{unrealModuleName}}, Error, TEXT("{{classname}}: Endpoint Url is not set, request cannot be performed"));
return false;
}
return true;
}
void {{classname}}::HandleResponse(FHttpResponsePtr HttpResponse, bool bSucceeded, Response& InOutResponse) const
{
InOutResponse.SetHttpResponse(HttpResponse);
InOutResponse.SetSuccessful(bSucceeded);
if (bSucceeded && HttpResponse.IsValid())
{
InOutResponse.SetHttpResponseCode((EHttpResponseCodes::Type)HttpResponse->GetResponseCode());
FString ContentType = HttpResponse->GetContentType();
FString Content;
if (ContentType == TEXT("application/json"))
{
Content = HttpResponse->GetContentAsString();
TSharedPtr<FJsonValue> JsonValue;
auto Reader = TJsonReaderFactory<>::Create(Content);
if (FJsonSerializer::Deserialize(Reader, JsonValue) && JsonValue.IsValid())
{
if (InOutResponse.FromJson(JsonValue))
return; // Successfully parsed
}
}
else if(ContentType == TEXT("text/plain"))
{
Content = HttpResponse->GetContentAsString();
InOutResponse.SetResponseString(Content);
return; // Successfully parsed
}
// Report the parse error but do not mark the request as unsuccessful. Data could be partial or malformed, but the request succeeded.
UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Failed to deserialize Http response content (type:%s):\n%s"), *ContentType , *Content);
return;
}
// By default, assume we failed to establish connection
InOutResponse.SetHttpResponseCode(EHttpResponseCodes::RequestTimeout);
}
{{#operations}}
{{#operation}}
bool {{classname}}::{{operationIdCamelCase}}(const {{operationIdCamelCase}}Request& Request, const F{{operationIdCamelCase}}Delegate& Delegate /*= F{{operationIdCamelCase}}Delegate()*/) const
{
if (!IsValid())
return false;
TSharedRef<IHttpRequest> HttpRequest = FHttpModule::Get().CreateRequest();
HttpRequest->SetURL(*(Url + Request.ComputePath()));
for(const auto& It : AdditionalHeaderParams)
{
HttpRequest->SetHeader(It.Key, It.Value);
}
Request.SetupHttpRequest(HttpRequest);
HttpRequest->OnProcessRequestComplete().BindRaw(this, &{{classname}}::On{{operationIdCamelCase}}Response, Delegate);
return HttpRequest->ProcessRequest();
}
void {{classname}}::On{{operationIdCamelCase}}Response(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded, F{{operationIdCamelCase}}Delegate Delegate) const
{
{{operationIdCamelCase}}Response Response;
HandleResponse(HttpResponse, bSucceeded, Response);
Delegate.ExecuteIfBound(Response);
}
{{/operation}}
{{/operations}}
{{#cppNamespaceDeclarations}}
}
{{/cppNamespaceDeclarations}}

View File

@@ -0,0 +1,405 @@
{{>licenseInfo}}
#pragma once
#include "{{modelNamePrefix}}BaseModel.h"
#include "Serialization/JsonSerializer.h"
#include "Dom/JsonObject.h"
#include "Misc/Base64.h"
class IHttpRequest;
{{#cppNamespaceDeclarations}}
namespace {{this}}
{
{{/cppNamespaceDeclarations}}
typedef TSharedRef<TJsonWriter<>> JsonWriter;
//////////////////////////////////////////////////////////////////////////
class {{dllapi}} HttpFileInput
{
public:
HttpFileInput(const TCHAR* InFilePath);
HttpFileInput(const FString& InFilePath);
// This will automatically set the content type if not already set
void SetFilePath(const TCHAR* InFilePath);
void SetFilePath(const FString& InFilePath);
// Optional if it can be deduced from the FilePath
void SetContentType(const TCHAR* ContentType);
HttpFileInput& operator=(const HttpFileInput& Other) = default;
HttpFileInput& operator=(const FString& InFilePath) { SetFilePath(*InFilePath); return*this; }
HttpFileInput& operator=(const TCHAR* InFilePath) { SetFilePath(InFilePath); return*this; }
const FString& GetFilePath() const { return FilePath; }
const FString& GetContentType() const { return ContentType; }
// Returns the filename with extension
FString GetFilename() const;
private:
FString FilePath;
FString ContentType;
};
//////////////////////////////////////////////////////////////////////////
class HttpMultipartFormData
{
public:
void SetBoundary(const TCHAR* InBoundary);
void SetupHttpRequest(const TSharedRef<IHttpRequest>& HttpRequest);
void AddStringPart(const TCHAR* Name, const TCHAR* Data);
void AddJsonPart(const TCHAR* Name, const FString& JsonString);
void AddBinaryPart(const TCHAR* Name, const TArray<uint8>& ByteArray);
void AddFilePart(const TCHAR* Name, const HttpFileInput& File);
private:
void AppendString(const TCHAR* Str);
const FString& GetBoundary() const;
mutable FString Boundary;
TArray<uint8> FormData;
static const TCHAR* Delimiter;
static const TCHAR* Newline;
};
//////////////////////////////////////////////////////////////////////////
// Decodes Base64Url encoded strings, see https://en.wikipedia.org/wiki/Base64#Variants_summary_table
template<typename T>
bool Base64UrlDecode(const FString& Base64String, T& Value)
{
FString TmpCopy(Base64String);
TmpCopy.ReplaceInline(TEXT("-"), TEXT("+"));
TmpCopy.ReplaceInline(TEXT("_"), TEXT("/"));
return FBase64::Decode(TmpCopy, Value);
}
// Encodes strings in Base64Url, see https://en.wikipedia.org/wiki/Base64#Variants_summary_table
template<typename T>
FString Base64UrlEncode(const T& Value)
{
FString Base64String = FBase64::Encode(Value);
Base64String.ReplaceInline(TEXT("+"), TEXT("-"));
Base64String.ReplaceInline(TEXT("/"), TEXT("_"));
return Base64String;
}
template<typename T>
inline FStringFormatArg ToStringFormatArg(const T& Value)
{
return FStringFormatArg(Value);
}
inline FStringFormatArg ToStringFormatArg(const FDateTime& Value)
{
return FStringFormatArg(Value.ToIso8601());
}
inline FStringFormatArg ToStringFormatArg(const TArray<uint8>& Value)
{
return FStringFormatArg(Base64UrlEncode(Value));
}
template<typename T, typename std::enable_if<!std::is_base_of<Model, T>::value, int>::type = 0>
inline FString ToString(const T& Value)
{
return FString::Format(TEXT("{0}"), { ToStringFormatArg(Value) });
}
inline FString ToString(const FString& Value)
{
return Value;
}
inline FString ToString(const TArray<uint8>& Value)
{
return Base64UrlEncode(Value);
}
inline FString ToString(const Model& Value)
{
FString String;
JsonWriter Writer = TJsonWriterFactory<>::Create(&String);
Value.WriteJson(Writer);
Writer->Close();
return String;
}
template<typename T>
inline FString ToUrlString(const T& Value)
{
return FPlatformHttp::UrlEncode(ToString(Value));
}
template<typename T>
inline FString CollectionToUrlString(const TArray<T>& Collection, const TCHAR* Separator)
{
FString Output;
if(Collection.Num() == 0)
return Output;
Output += ToUrlString(Collection[0]);
for(int i = 1; i < Collection.Num(); i++)
{
Output += FString::Format(TEXT("{0}{1}"), { Separator, *ToUrlString(Collection[i]) });
}
return Output;
}
template<typename T>
inline FString CollectionToUrlString_csv(const TArray<T>& Collection, const TCHAR* BaseName)
{
return CollectionToUrlString(Collection, TEXT(","));
}
template<typename T>
inline FString CollectionToUrlString_ssv(const TArray<T>& Collection, const TCHAR* BaseName)
{
return CollectionToUrlString(Collection, TEXT(" "));
}
template<typename T>
inline FString CollectionToUrlString_tsv(const TArray<T>& Collection, const TCHAR* BaseName)
{
return CollectionToUrlString(Collection, TEXT("\t"));
}
template<typename T>
inline FString CollectionToUrlString_pipes(const TArray<T>& Collection, const TCHAR* BaseName)
{
return CollectionToUrlString(Collection, TEXT("|"));
}
template<typename T>
inline FString CollectionToUrlString_multi(const TArray<T>& Collection, const TCHAR* BaseName)
{
FString Output;
if(Collection.Num() == 0)
return Output;
Output += FString::Format(TEXT("{0}={1}"), { FStringFormatArg(BaseName), ToUrlString(Collection[0]) });
for(int i = 1; i < Collection.Num(); i++)
{
Output += FString::Format(TEXT("&{0}={1}"), { FStringFormatArg(BaseName), ToUrlString(Collection[i]) });
}
return Output;
}
//////////////////////////////////////////////////////////////////////////
template<typename T, typename std::enable_if<!std::is_base_of<Model, T>::value, int>::type = 0>
inline void WriteJsonValue(JsonWriter& Writer, const T& Value)
{
Writer->WriteValue(Value);
}
inline void WriteJsonValue(JsonWriter& Writer, const FDateTime& Value)
{
Writer->WriteValue(Value.ToIso8601());
}
inline void WriteJsonValue(JsonWriter& Writer, const Model& Value)
{
Value.WriteJson(Writer);
}
template<typename T>
inline void WriteJsonValue(JsonWriter& Writer, const TArray<T>& Value)
{
Writer->WriteArrayStart();
for (const auto& Element : Value)
{
WriteJsonValue(Writer, Element);
}
Writer->WriteArrayEnd();
}
template<typename T>
inline void WriteJsonValue(JsonWriter& Writer, const TMap<FString, T>& Value)
{
Writer->WriteObjectStart();
for (const auto& It : Value)
{
Writer->WriteIdentifierPrefix(It.Key);
WriteJsonValue(Writer, It.Value);
}
Writer->WriteObjectEnd();
}
inline void WriteJsonValue(JsonWriter& Writer, const TSharedPtr<FJsonObject>& Value)
{
if (Value.IsValid())
{
FJsonSerializer::Serialize(Value.ToSharedRef(), Writer, false);
}
else
{
Writer->WriteObjectStart();
Writer->WriteObjectEnd();
}
}
inline void WriteJsonValue(JsonWriter& Writer, const TArray<uint8>& Value)
{
Writer->WriteValue(ToString(Value));
}
//////////////////////////////////////////////////////////////////////////
template<typename T>
inline bool TryGetJsonValue(const TSharedPtr<FJsonObject>& JsonObject, const FString& Key, T& Value)
{
const TSharedPtr<FJsonValue> JsonValue = JsonObject->TryGetField(Key);
if (JsonValue.IsValid() && !JsonValue->IsNull())
{
return TryGetJsonValue(JsonValue, Value);
}
return false;
}
template<typename T>
inline bool TryGetJsonValue(const TSharedPtr<FJsonObject>& JsonObject, const FString& Key, TOptional<T>& OptionalValue)
{
if(JsonObject->HasField(Key))
{
T Value;
if (TryGetJsonValue(JsonObject, Key, Value))
{
OptionalValue = Value;
return true;
}
else
return false;
}
return true; // Absence of optional value is not a parsing error
}
inline bool TryGetJsonValue(const TSharedPtr<FJsonValue>& JsonValue, FString& Value)
{
FString TmpValue;
if (JsonValue->TryGetString(TmpValue))
{
Value = TmpValue;
return true;
}
else
return false;
}
inline bool TryGetJsonValue(const TSharedPtr<FJsonValue>& JsonValue, FDateTime& Value)
{
FString TmpValue;
if (JsonValue->TryGetString(TmpValue))
return FDateTime::Parse(TmpValue, Value);
else
return false;
}
inline bool TryGetJsonValue(const TSharedPtr<FJsonValue>& JsonValue, bool& Value)
{
bool TmpValue;
if (JsonValue->TryGetBool(TmpValue))
{
Value = TmpValue;
return true;
}
else
return false;
}
template<typename T, typename std::enable_if<!std::is_base_of<Model, T>::value, int>::type = 0>
inline bool TryGetJsonValue(const TSharedPtr<FJsonValue>& JsonValue, T& Value)
{
T TmpValue;
if (JsonValue->TryGetNumber(TmpValue))
{
Value = TmpValue;
return true;
}
else
return false;
}
inline bool TryGetJsonValue(const TSharedPtr<FJsonValue>& JsonValue, Model& Value)
{
const TSharedPtr<FJsonObject>* Object;
if (JsonValue->TryGetObject(Object))
return Value.FromJson(*Object);
else
return false;
}
template<typename T>
inline bool TryGetJsonValue(const TSharedPtr<FJsonValue>& JsonValue, TArray<T>& ArrayValue)
{
const TArray<TSharedPtr<FJsonValue>>* JsonArray;
if (JsonValue->TryGetArray(JsonArray))
{
bool ParseSuccess = true;
const int32 Count = JsonArray->Num();
ArrayValue.Reset(Count);
for (int i = 0; i < Count; i++)
{
T TmpValue;
ParseSuccess &= TryGetJsonValue((*JsonArray)[i], TmpValue);
ArrayValue.Emplace(MoveTemp(TmpValue));
}
return ParseSuccess;
}
return false;
}
template<typename T>
inline bool TryGetJsonValue(const TSharedPtr<FJsonValue>& JsonValue, TMap<FString, T>& MapValue)
{
const TSharedPtr<FJsonObject>* Object;
if (JsonValue->TryGetObject(Object))
{
MapValue.Reset();
bool ParseSuccess = true;
for (const auto& It : (*Object)->Values)
{
T TmpValue;
ParseSuccess &= TryGetJsonValue(It.Value, TmpValue);
MapValue.Emplace(It.Key, MoveTemp(TmpValue));
}
return ParseSuccess;
}
return false;
}
inline bool TryGetJsonValue(const TSharedPtr<FJsonValue>& JsonValue, TSharedPtr<FJsonObject>& JsonObjectValue)
{
const TSharedPtr<FJsonObject>* Object;
if (JsonValue->TryGetObject(Object))
{
JsonObjectValue = *Object;
return true;
}
return false;
}
inline bool TryGetJsonValue(const TSharedPtr<FJsonValue>& JsonValue, TArray<uint8>& Value)
{
FString TmpValue;
if (JsonValue->TryGetString(TmpValue))
{
Base64UrlDecode(TmpValue, Value);
return true;
}
else
return false;
}
{{#cppNamespaceDeclarations}}
}
{{/cppNamespaceDeclarations}}

View File

@@ -0,0 +1,187 @@
{{>licenseInfo}}
#include "{{modelNamePrefix}}Helpers.h"
#include "{{unrealModuleName}}Module.h"
#include "Interfaces/IHttpRequest.h"
#include "PlatformHttp.h"
#include "Misc/FileHelper.h"
{{#cppNamespaceDeclarations}}
namespace {{this}}
{
{{/cppNamespaceDeclarations}}
HttpFileInput::HttpFileInput(const TCHAR* InFilePath)
{
SetFilePath(InFilePath);
}
HttpFileInput::HttpFileInput(const FString& InFilePath)
{
SetFilePath(InFilePath);
}
void HttpFileInput::SetFilePath(const TCHAR* InFilePath)
{
FilePath = InFilePath;
if(ContentType.IsEmpty())
{
ContentType = FPlatformHttp::GetMimeType(InFilePath);
}
}
void HttpFileInput::SetFilePath(const FString& InFilePath)
{
SetFilePath(*InFilePath);
}
void HttpFileInput::SetContentType(const TCHAR* InContentType)
{
ContentType = InContentType;
}
FString HttpFileInput::GetFilename() const
{
return FPaths::GetCleanFilename(FilePath);
}
//////////////////////////////////////////////////////////////////////////
const TCHAR* HttpMultipartFormData::Delimiter = TEXT("--");
const TCHAR* HttpMultipartFormData::Newline = TEXT("\r\n");
void HttpMultipartFormData::SetBoundary(const TCHAR* InBoundary)
{
checkf(Boundary.IsEmpty(), TEXT("Boundary must be set before usage"));
Boundary = InBoundary;
}
const FString& HttpMultipartFormData::GetBoundary() const
{
if (Boundary.IsEmpty())
{
// Generate a random boundary with enough entropy, should avoid occurences of the boundary in the data.
// Since the boundary is generated at every request, in case of failure, retries should succeed.
Boundary = FGuid::NewGuid().ToString(EGuidFormats::Short);
}
return Boundary;
}
void HttpMultipartFormData::SetupHttpRequest(const TSharedRef<IHttpRequest>& HttpRequest)
{
if(HttpRequest->GetVerb() != TEXT("POST"))
{
UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Expected POST verb when using multipart form data"));
}
// Append final boundary
AppendString(Delimiter);
AppendString(*GetBoundary());
AppendString(Delimiter);
HttpRequest->SetHeader("Content-Type", FString::Printf(TEXT("multipart/form-data; boundary=%s"), *GetBoundary()));
HttpRequest->SetContent(FormData);
}
void HttpMultipartFormData::AddStringPart(const TCHAR* Name, const TCHAR* Data)
{
// Add boundary
AppendString(Delimiter);
AppendString(*GetBoundary());
AppendString(Newline);
// Add header
AppendString(*FString::Printf(TEXT("Content-Disposition: form-data; name = \"%s\""), Name));
AppendString(Newline);
AppendString(*FString::Printf(TEXT("Content-Type: text/plain; charset=utf-8")));
AppendString(Newline);
// Add header to body splitter
AppendString(Newline);
// Add Data
AppendString(Data);
AppendString(Newline);
}
void HttpMultipartFormData::AddJsonPart(const TCHAR* Name, const FString& JsonString)
{
// Add boundary
AppendString(Delimiter);
AppendString(*GetBoundary());
AppendString(Newline);
// Add header
AppendString(*FString::Printf(TEXT("Content-Disposition: form-data; name=\"%s\""), Name));
AppendString(Newline);
AppendString(*FString::Printf(TEXT("Content-Type: application/json; charset=utf-8")));
AppendString(Newline);
// Add header to body splitter
AppendString(Newline);
// Add Data
AppendString(*JsonString);
AppendString(Newline);
}
void HttpMultipartFormData::AddBinaryPart(const TCHAR* Name, const TArray<uint8>& ByteArray)
{
// Add boundary
AppendString(Delimiter);
AppendString(*GetBoundary());
AppendString(Newline);
// Add header
AppendString(*FString::Printf(TEXT("Content-Disposition: form-data; name=\"%s\""), Name));
AppendString(Newline);
AppendString(*FString::Printf(TEXT("Content-Type: application/octet-stream")));
AppendString(Newline);
// Add header to body splitter
AppendString(Newline);
// Add Data
FormData.Append(ByteArray);
AppendString(Newline);
}
void HttpMultipartFormData::AddFilePart(const TCHAR* Name, const HttpFileInput& File)
{
TArray<uint8> FileContents;
if (!FFileHelper::LoadFileToArray(FileContents, *File.GetFilePath()))
{
UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Failed to load file (%s)"), *File.GetFilePath());
return;
}
// Add boundary
AppendString(Delimiter);
AppendString(*GetBoundary());
AppendString(Newline);
// Add header
AppendString(*FString::Printf(TEXT("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\""), Name, *File.GetFilename()));
AppendString(Newline);
AppendString(*FString::Printf(TEXT("Content-Type: %s"), *File.GetContentType()));
AppendString(Newline);
// Add header to body splitter
AppendString(Newline);
// Add Data
FormData.Append(FileContents);
AppendString(Newline);
}
void HttpMultipartFormData::AppendString(const TCHAR* Str)
{
FTCHARToUTF8 utf8Str(Str);
FormData.Append((uint8*)utf8Str.Get(), utf8Str.Length());
}
{{#cppNamespaceDeclarations}}
}
{{/cppNamespaceDeclarations}}

View File

@@ -0,0 +1,11 @@
/**
* {{{appName}}}
* {{{appDescription}}}
*
* {{#version}}OpenAPI spec version: {{{version}}}{{/version}}
* {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}}
*
* NOTE: This class is auto generated by OpenAPI Generator
* https://github.com/OpenAPITools/openapi-generator
* Do not edit the class manually.
*/

View File

@@ -0,0 +1,59 @@
{{>licenseInfo}}
#pragma once
#include "Interfaces/IHttpRequest.h"
#include "Interfaces/IHttpResponse.h"
#include "Serialization/JsonWriter.h"
#include "Dom/JsonObject.h"
{{#cppNamespaceDeclarations}}
namespace {{this}}
{
{{/cppNamespaceDeclarations}}
typedef TSharedRef<TJsonWriter<>> JsonWriter;
class {{dllapi}} Model
{
public:
virtual ~Model() {}
virtual void WriteJson(JsonWriter& Writer) const = 0;
virtual bool FromJson(const TSharedPtr<FJsonObject>& JsonObject) = 0;
};
class {{dllapi}} Request
{
public:
virtual ~Request() {}
virtual void SetupHttpRequest(const TSharedRef<IHttpRequest>& HttpRequest) const = 0;
virtual FString ComputePath() const = 0;
};
class {{dllapi}} Response
{
public:
virtual ~Response() {}
virtual bool FromJson(const TSharedPtr<FJsonValue>& JsonObject) = 0;
void SetSuccessful(bool InSuccessful) { Successful = InSuccessful; }
bool IsSuccessful() const { return Successful; }
virtual void SetHttpResponseCode(EHttpResponseCodes::Type InHttpResponseCode);
EHttpResponseCodes::Type GetHttpResponseCode() const { return ResponseCode; }
void SetResponseString(const FString& InResponseString) { ResponseString = InResponseString; }
const FString& GetResponseString() const { return ResponseString; }
void SetHttpResponse(const FHttpResponsePtr& InHttpResponse) { HttpResponse = InHttpResponse; }
const FHttpResponsePtr& GetHttpResponse() const { return HttpResponse; }
private:
bool Successful;
EHttpResponseCodes::Type ResponseCode;
FString ResponseString;
FHttpResponsePtr HttpResponse;
};
{{#cppNamespaceDeclarations}}
}
{{/cppNamespaceDeclarations}}

View File

@@ -0,0 +1,21 @@
{{>licenseInfo}}
#include "{{modelNamePrefix}}BaseModel.h"
{{#cppNamespaceDeclarations}}
namespace {{this}}
{
{{/cppNamespaceDeclarations}}
void Response::SetHttpResponseCode(EHttpResponseCodes::Type InHttpResponseCode)
{
ResponseCode = InHttpResponseCode;
SetSuccessful(EHttpResponseCodes::IsOk(InHttpResponseCode));
if(InHttpResponseCode == EHttpResponseCodes::RequestTimeout)
{
SetResponseString(TEXT("Request Timeout"));
}
}
{{#cppNamespaceDeclarations}}
}
{{/cppNamespaceDeclarations}}

View File

@@ -0,0 +1,51 @@
{{>licenseInfo}}
#pragma once
#include "{{modelNamePrefix}}BaseModel.h"
{{#imports}}{{{import}}}
{{/imports}}
{{#cppNamespaceDeclarations}}
namespace {{this}}
{
{{/cppNamespaceDeclarations}}
{{#models}}
{{#model}}
/*
* {{classname}}
*
* {{description}}
*/
class {{dllapi}} {{classname}} : public Model
{
public:
virtual ~{{classname}}() {}
bool FromJson(const TSharedPtr<FJsonObject>& JsonObject) final;
void WriteJson(JsonWriter& Writer) const final;
{{#vars}}
{{#isEnum}}
{{#allowableValues}}
enum class {{{enumName}}}
{
{{#enumVars}}
{{name}},
{{/enumVars}}
};
{{/allowableValues}}
{{#description}}/* {{{description}}} */
{{/description}}{{^required}}TOptional<{{/required}}{{{datatypeWithEnum}}}{{^required}}>{{/required}} {{name}}{{#required}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}{{/required}};
{{/isEnum}}
{{^isEnum}}
{{#description}}/* {{{description}}} */
{{/description}}{{^required}}TOptional<{{/required}}{{{datatype}}}{{^required}}>{{/required}} {{name}}{{#required}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}{{/required}};
{{/isEnum}}
{{/vars}}
};
{{/model}}
{{/models}}
{{#cppNamespaceDeclarations}}
}
{{/cppNamespaceDeclarations}}

View File

@@ -0,0 +1,98 @@
{{>licenseInfo}}
#include "{{classname}}.h"
#include "{{unrealModuleName}}Module.h"
#include "{{modelNamePrefix}}Helpers.h"
#include "Templates/SharedPointer.h"
{{#cppNamespaceDeclarations}}
namespace {{this}}
{
{{/cppNamespaceDeclarations}}
{{#models}}{{#model}}
{{#hasEnums}}
{{#vars}}
{{#isEnum}}
inline FString ToString(const {{classname}}::{{{enumName}}}& Value)
{
{{#allowableValues}}
switch (Value)
{
{{#enumVars}}
case {{classname}}::{{{enumName}}}::{{name}}:
return TEXT({{{value}}});
{{/enumVars}}
}
{{/allowableValues}}
UE_LOG(Log{{unrealModuleName}}, Error, TEXT("Invalid {{classname}}::{{{enumName}}} Value (%d)"), (int)Value);
return TEXT("");
}
inline FStringFormatArg ToStringFormatArg(const {{classname}}::{{{enumName}}}& Value)
{
return FStringFormatArg(ToString(Value));
}
inline void WriteJsonValue(JsonWriter& Writer, const {{classname}}::{{{enumName}}}& Value)
{
WriteJsonValue(Writer, ToString(Value));
}
inline bool TryGetJsonValue(const TSharedPtr<FJsonValue>& JsonValue, {{classname}}::{{{enumName}}}& Value)
{
FString TmpValue;
if (JsonValue->TryGetString(TmpValue))
{
static TMap<FString, {{classname}}::{{{enumName}}}> StringToEnum = { {{#enumVars}}
{ TEXT({{{value}}}), {{classname}}::{{{enumName}}}::{{name}} },{{/enumVars}} };
const auto Found = StringToEnum.Find(TmpValue);
if(Found)
{
Value = *Found;
return true;
}
}
return false;
}
{{/isEnum}}
{{/vars}}
{{/hasEnums}}
void {{classname}}::WriteJson(JsonWriter& Writer) const
{
{{#parent}}
#error inheritance not handled right now
{{/parent}}
Writer->WriteObjectStart();
{{#vars}}
{{#required}}
Writer->WriteIdentifierPrefix(TEXT("{{baseName}}")); WriteJsonValue(Writer, {{name}});
{{/required}}
{{^required}}
if ({{name}}.IsSet())
{
Writer->WriteIdentifierPrefix(TEXT("{{baseName}}")); WriteJsonValue(Writer, {{name}}.GetValue());
}
{{/required}}
{{/vars}}
Writer->WriteObjectEnd();
}
bool {{classname}}::FromJson(const TSharedPtr<FJsonObject>& JsonObject)
{
bool ParseSuccess = true;
{{#vars}}
ParseSuccess &= TryGetJsonValue(JsonObject, TEXT("{{baseName}}"), {{name}});
{{/vars}}
return ParseSuccess;
}
{{/model}}
{{/models}}
{{#cppNamespaceDeclarations}}
}
{{/cppNamespaceDeclarations}}

View File

@@ -0,0 +1,15 @@
{{>licenseInfo}}
#pragma once
#include "Modules/ModuleInterface.h"
#include "Modules/ModuleManager.h"
#include "Logging/LogMacros.h"
DECLARE_LOG_CATEGORY_EXTERN(Log{{unrealModuleName}}, Log, All);
class {{dllapi}} {{unrealModuleName}}Module : public IModuleInterface
{
public:
void StartupModule() final;
void ShutdownModule() final;
};

View File

@@ -0,0 +1,14 @@
{{>licenseInfo}}
#include "{{unrealModuleName}}Module.h"
IMPLEMENT_MODULE({{unrealModuleName}}Module, {{unrealModuleName}});
DEFINE_LOG_CATEGORY(Log{{unrealModuleName}});
void {{unrealModuleName}}Module::StartupModule()
{
}
void {{unrealModuleName}}Module::ShutdownModule()
{
}