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

[php-slim4] Decouple Slim4 codegen from Slim3 #9208

Merged
merged 1 commit into from
May 24, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@

package org.openapitools.codegen.languages;

import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.CodegenType;
import org.openapitools.codegen.CodegenOperation;
import org.openapitools.codegen.CodegenSecurity;
import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.SupportingFile;
Expand All @@ -26,12 +34,22 @@
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;

public class PhpSlim4ServerCodegen extends PhpSlimServerCodegen {
import static org.openapitools.codegen.utils.StringUtils.camelize;

public class PhpSlim4ServerCodegen extends AbstractPhpCodegen {
private final Logger LOGGER = LoggerFactory.getLogger(PhpSlim4ServerCodegen.class);

public static final String USER_CLASSNAME_KEY = "userClassname";
public static final String PSR7_IMPLEMENTATION = "psr7Implementation";

protected String groupId = "org.openapitools";
protected String artifactId = "openapi-server";
protected String authDirName = "Auth";
protected String authPackage = "";
protected String psr7Implementation = "slim-psr7";
protected String interfacesDirName = "Interfaces";
protected String interfacesPackage = "";
Expand All @@ -40,17 +58,48 @@ public PhpSlim4ServerCodegen() {
super();

modifyFeatureSet(features -> features
.includeDocumentationFeatures(DocumentationFeature.Readme)
.wireFormatFeatures(EnumSet.of(WireFormatFeature.JSON, WireFormatFeature.XML))
.securityFeatures(EnumSet.noneOf(SecurityFeature.class))
.excludeGlobalFeatures(
GlobalFeature.XMLStructureDefinitions,
GlobalFeature.Callbacks,
GlobalFeature.LinkObjects,
GlobalFeature.ParameterStyling
)
.excludeSchemaSupportFeatures(
SchemaSupportFeature.Polymorphism
)
.includeClientModificationFeatures(ClientModificationFeature.MockServer)
);

generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
.stability(Stability.STABLE)
.build();

// clear import mapping (from default generator) as slim does not use it
// at the moment
importMapping.clear();

variableNamingConvention = "camelCase";
artifactVersion = "1.0.0";
setInvokerPackage("OpenAPIServer");
apiPackage = invokerPackage + "\\" + apiDirName;
modelPackage = invokerPackage + "\\" + modelDirName;
authPackage = invokerPackage + "\\" + authDirName;
interfacesPackage = invokerPackage + "\\" + interfacesDirName;
outputFolder = "generated-code" + File.separator + "slim4";

modelTestTemplateFiles.put("model_test.mustache", ".php");
// no doc files
modelDocTemplateFiles.clear();
apiDocTemplateFiles.clear();

embeddedTemplateDir = templateDir = "php-slim4-server";

additionalProperties.put(CodegenConstants.GROUP_ID, groupId);
additionalProperties.put(CodegenConstants.ARTIFACT_ID, artifactId);

// override cliOptions from AbstractPhpCodegen
updateOption(AbstractPhpCodegen.VARIABLE_NAMING_CONVENTION, "camelCase");

Expand All @@ -68,6 +117,11 @@ public PhpSlim4ServerCodegen() {
cliOptions.add(psr7Option);
}

@Override
public CodegenType getTag() {
return CodegenType.SERVER;
}

@Override
public String getName() {
return "php-slim4";
Expand All @@ -78,15 +132,39 @@ public String getHelp() {
return "Generates a PHP Slim 4 Framework server library(with Mock server).";
}

@Override
public String apiFileFolder() {
if (apiPackage.startsWith(invokerPackage + "\\")) {
// need to strip out invokerPackage from path
return (outputFolder + File.separator + toSrcPath(StringUtils.removeStart(apiPackage, invokerPackage + "\\"), srcBasePath));
}
return (outputFolder + File.separator + toSrcPath(apiPackage, srcBasePath));
}

@Override
public String modelFileFolder() {
if (modelPackage.startsWith(invokerPackage + "\\")) {
// need to strip out invokerPackage from path
return (outputFolder + File.separator + toSrcPath(StringUtils.removeStart(modelPackage, invokerPackage + "\\"), srcBasePath));
}
return (outputFolder + File.separator + toSrcPath(modelPackage, srcBasePath));
}

@Override
public void processOpts() {
super.processOpts();

if (additionalProperties.containsKey(CodegenConstants.INVOKER_PACKAGE)) {
// Update the invokerPackage for the default authPackage
authPackage = invokerPackage + "\\" + authDirName;
// Update interfacesPackage
interfacesPackage = invokerPackage + "\\" + interfacesDirName;
}

// make auth src path available in mustache template
additionalProperties.put("authPackage", authPackage);
additionalProperties.put("authSrcPath", "./" + toSrcPath(authPackage, srcBasePath));

// same for interfaces package
additionalProperties.put("interfacesPackage", interfacesPackage);
additionalProperties.put("interfacesSrcPath", "./" + toSrcPath(interfacesPackage, srcBasePath));
Expand Down Expand Up @@ -121,6 +199,14 @@ public void processOpts() {
additionalProperties.put("isSlimPsr7", Boolean.TRUE);
}

supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
supportingFiles.add(new SupportingFile("composer.mustache", "", "composer.json"));
supportingFiles.add(new SupportingFile("index.mustache", "", "index.php"));
supportingFiles.add(new SupportingFile(".htaccess", "", ".htaccess"));
supportingFiles.add(new SupportingFile("SlimRouter.mustache", toSrcPath(invokerPackage, srcBasePath), "SlimRouter.php"));
supportingFiles.add(new SupportingFile("phpunit.xml.mustache", "", "phpunit.xml.dist"));
supportingFiles.add(new SupportingFile("phpcs.xml.mustache", "", "phpcs.xml.dist"));

// Slim 4 doesn't parse JSON body anymore we need to add suggested middleware
// ref: https://www.slimframework.com/docs/v4/objects/request.html#the-request-body
supportingFiles.add(new SupportingFile("htaccess_deny_all", "config", ".htaccess"));
Expand All @@ -131,6 +217,126 @@ public void processOpts() {
supportingFiles.add(new SupportingFile("base_model_test.mustache", toSrcPath(invokerPackage, testBasePath), "BaseModelTest.php"));
}

@Override
public Map<String, Object> postProcessOperationsWithModels(Map<String, Object> objs, List<Object> allModels) {
Map<String, Object> operations = (Map<String, Object>) objs.get("operations");
List<CodegenOperation> operationList = (List<CodegenOperation>) operations.get("operation");
addUserClassnameToOperations(operations);
escapeMediaType(operationList);
return objs;
}

@Override
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
Map<String, Object> apiInfo = (Map<String, Object>) objs.get("apiInfo");
List<HashMap<String, Object>> apiList = (List<HashMap<String, Object>>) apiInfo.get("apis");
for (HashMap<String, Object> api : apiList) {
HashMap<String, Object> operations = (HashMap<String, Object>) api.get("operations");
List<CodegenOperation> operationList = (List<CodegenOperation>) operations.get("operation");

// Sort operations to avoid static routes shadowing
// ref: https://github.com/nikic/FastRoute/blob/master/src/DataGenerator/RegexBasedAbstract.php#L92-L101
Collections.sort(operationList, new Comparator<CodegenOperation>() {
@Override
public int compare(CodegenOperation one, CodegenOperation another) {
if (one.getHasPathParams() && !another.getHasPathParams()) return 1;
if (!one.getHasPathParams() && another.getHasPathParams()) return -1;
return 0;
}
});
}
return objs;
}

@Override
public List<CodegenSecurity> fromSecurity(Map<String, SecurityScheme> securitySchemeMap) {
List<CodegenSecurity> codegenSecurities = super.fromSecurity(securitySchemeMap);
if (Boolean.FALSE.equals(codegenSecurities.isEmpty())) {
supportingFiles.add(new SupportingFile("abstract_authenticator.mustache", toSrcPath(authPackage, srcBasePath), toAbstractName("Authenticator") + ".php"));
}
return codegenSecurities;
}

@Override
public String toApiName(String name) {
if (name.length() == 0) {
return toAbstractName("DefaultApi");
}
return toAbstractName(camelize(name) + "Api");
}

@Override
public String toApiTestFilename(String name) {
if (name.length() == 0) {
return "DefaultApiTest";
}
return camelize(name) + "ApiTest";
}

/**
* Strips out abstract prefix and suffix from classname and puts it in "userClassname" property of operations object.
*
* @param operations codegen object with operations
*/
private void addUserClassnameToOperations(Map<String, Object> operations) {
String classname = (String) operations.get("classname");
classname = classname.replaceAll("^" + abstractNamePrefix, "");
classname = classname.replaceAll(abstractNameSuffix + "$", "");
operations.put(USER_CLASSNAME_KEY, classname);
}

@Override
public String encodePath(String input) {
if (input == null) {
return input;
}

// from DefaultCodegen.java
// remove \t, \n, \r
// replace \ with \\
// replace " with \"
// outter unescape to retain the original multi-byte characters
// finally escalate characters avoiding code injection
input = super.escapeUnsafeCharacters(
StringEscapeUtils.unescapeJava(
StringEscapeUtils.escapeJava(input)
.replace("\\/", "/"))
.replaceAll("[\\t\\n\\r]", " ")
.replace("\\", "\\\\"));
// .replace("\"", "\\\""));

// from AbstractPhpCodegen.java
// Trim the string to avoid leading and trailing spaces.
input = input.trim();
try {

input = URLEncoder.encode(input, "UTF-8")
.replaceAll("\\+", "%20")
.replaceAll("\\%2F", "/")
.replaceAll("\\%7B", "{") // keep { part of complex placeholders
.replaceAll("\\%7D", "}") // } part
.replaceAll("\\%5B", "[") // [ part
.replaceAll("\\%5D", "]") // ] part
.replaceAll("\\%3A", ":") // : part
.replaceAll("\\%2B", "+") // + part
.replaceAll("\\%5C\\%5Cd", "\\\\d"); // \d part
} catch (UnsupportedEncodingException e) {
// continue
LOGGER.error(e.getMessage(), e);
}
return input;
}

@Override
public CodegenOperation fromOperation(String path,
String httpMethod,
Operation operation,
List<Server> servers) {
CodegenOperation op = super.fromOperation(path, httpMethod, operation, servers);
op.path = encodePath(path);
return op;
}

/**
* Set PSR-7 implementation package.
* Ref: https://www.slimframework.com/docs/v4/concepts/value-objects.html
Expand Down