diff --git a/docs/generators/typescript-angular.md b/docs/generators/typescript-angular.md
index 9192d4c1a1f..7fde34c39a3 100644
--- a/docs/generators/typescript-angular.md
+++ b/docs/generators/typescript-angular.md
@@ -232,7 +232,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|XMLStructureDefinitions|✗|OAS2,OAS3
|MultiServer|✗|OAS3
|ParameterizedServer|✗|OAS3
-|ParameterStyling|✗|OAS3
+|ParameterStyling|✓|OAS3
|Callbacks|✗|OAS3
|LinkObjects|✗|OAS3
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java
index 5d223ca1aed..4cac98e9ffe 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractTypeScriptClientCodegen.java
@@ -24,6 +24,7 @@ import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
+import org.checkerframework.checker.nullness.qual.Nullable;
import org.openapitools.codegen.CodegenConstants.ENUM_PROPERTY_NAMING_TYPE;
import org.openapitools.codegen.CodegenConstants.MODEL_PROPERTY_NAMING_TYPE;
import org.openapitools.codegen.CodegenConstants.PARAM_NAMING_TYPE;
@@ -39,13 +40,183 @@ import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import static org.openapitools.codegen.languages.AbstractTypeScriptClientCodegen.ParameterExpander.ParamStyle.*;
import static org.openapitools.codegen.utils.StringUtils.camelize;
import static org.openapitools.codegen.utils.StringUtils.underscore;
public abstract class AbstractTypeScriptClientCodegen extends DefaultCodegen implements CodegenConfig {
+
+ /**
+ * Help generating code for any kind of URL parameters (path, matrix, query).
+ *
+ * Generators may use this class when substituting URL-parameter-placeholders
+ * with generated code:
+ *
+ *
+ * While parsing placeholders character-by-character, this class helps accumulating these characters and
+ * building the final placeholder name.
+ *
+ *
+ * With the placeholder name, this class tries to find the parameter in the operation.
+ * The class then uses this parameter to build code that is usable by the generator.
+ *
+ */
+ // TODO: this class grew quite large and most code now is specific to TypeScriptAngularClientCodeGen.java.
+ // => move code-generation to the concrete generator and let the generator pass this as lambda
+ @SuppressWarnings("StringBufferField")
+ public static class ParameterExpander {
+ private static final Pattern JS_QUOTE_PATTERN = Pattern.compile("\"");
+ private static final String JS_QUOTE_REPLACEMENT = "\\\"";
+
+ /**
+ * How the parameter is formatted/converted/encoded in the actual request.
+ *
+ * See e.g. OpenAPI 3.1 spec: Style Values.
+ *
+ */
+ public enum ParamStyle {
+ matrix,
+ label,
+ form,
+ simple,
+ spaceDelimited,
+ pipeDelimited,
+ deepObject;
+
+ public String asString() {
+ return name();
+ }
+ }
+
+ /**
+ * Where the parameter is located in the request.
+ *
+ * See e.g. OpenAPI 3.1 spec: Parameter Locations
+ *
+ */
+ public enum Location {
+ query((operation, param) -> operation.queryParams.contains(param), form),
+ header((operation, param) -> operation.headerParams.contains(param), simple),
+ path((operation, param) -> operation.pathParams.contains(param), simple),
+ cookie((operation, param) -> operation.cookieParams.contains(param), form);
+
+ public final ParamStyle defaultStyle;
+ public final BiPredicate isPresentIn;
+
+ Location(BiPredicate isPresentIn, ParamStyle defaultStyle) {
+ this.defaultStyle = defaultStyle;
+ this.isPresentIn = isPresentIn;
+ }
+
+ public String defaultStyle(@Nullable String style) {
+ if (style != null && !style.trim().isEmpty()) {
+ return style;
+ }
+ return defaultStyle.asString();
+ }
+
+ public static Location fromParam(CodegenOperation op, CodegenParameter param) {
+ return Arrays.stream(values())
+ .filter(v -> v.isPresentIn.test(op, param))
+ .findAny()
+ .orElseThrow(() -> new IllegalArgumentException(String.format(
+ Locale.ROOT,
+ "Cannot find param ' %s' in operation '%s'",
+ param, op.operationId
+ )));
+ }
+ }
+
+ private final StringBuilder parameterName = new StringBuilder();
+ private final CodegenOperation op;
+ private final Function toParameterName;
+
+ public ParameterExpander(CodegenOperation op, Function toParameterName) {
+ this.op = op;
+ this.toParameterName = toParameterName;
+ }
+
+ private void reset() {
+ parameterName.setLength(0);
+ }
+
+ public void appendToParameterName(char c) {
+ parameterName.append(c);
+ }
+
+ public String buildPathEntry() {
+ CodegenParameter parameter = findPathParameterByName();
+
+ String result = "";
+ if (parameter != null) {
+ String generatedParameterName = toParameterName.apply(parameterName.toString());
+ result = buildPathEntry(parameter, generatedParameterName);
+ }
+
+ reset();
+ return result;
+ }
+
+ private String buildPathEntry(CodegenParameter parameter, String generatedParameterName) {
+ Location location = Location.fromParam(op, parameter);
+ String style = location.defaultStyle(parameter.style);
+
+ String optsObject = String.format(
+ Locale.ROOT,
+ "{name: %s, value: %s, in: %s, style: %s, explode: %s, dataType: %s, dataFormat: %s}",
+ quotedJSString(parameter.paramName),
+ generatedParameterName,
+ quotedJSString(location.name()),
+ quotedJSString(style),
+ parameter.isExplode,
+ quotedJSString(parameter.dataType),
+ nullableQuotedJSString(parameter.dataFormat)
+ );
+
+ String result = String.format(Locale.ROOT, "${this.configuration.encodeParam(%s)}", optsObject);
+ return result;
+ }
+
+ private @Nullable CodegenParameter findPathParameterByName() {
+ for (CodegenParameter param : op.pathParams) {
+ if (param.baseName.equals(parameterName.toString())) {
+ return param;
+ }
+ }
+ return null;
+ }
+
+ private static String quotedJSString(String string) {
+ String escaped = escapeForQuotedJSString(string);
+ String result = '"' + escaped + '"';
+ return result;
+ }
+
+ protected static String escapeForQuotedJSString(String string) {
+ String quoted = JS_QUOTE_PATTERN.matcher(string)
+ .replaceAll(JS_QUOTE_REPLACEMENT);
+
+ return quoted;
+ }
+
+
+ protected String nullableQuotedJSString(@Nullable String string) {
+ if (string == null) {
+ return "undefined";
+ }
+
+ String result = quotedJSString(string);
+
+ return result;
+ }
+ }
+
private final Logger LOGGER = LoggerFactory.getLogger(AbstractTypeScriptClientCodegen.class);
private static final String X_DISCRIMINATOR_TYPE = "x-discriminator-value";
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java
index 1b084753a09..5fcf5a708e8 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java
@@ -20,6 +20,7 @@ package org.openapitools.codegen.languages;
import io.swagger.v3.oas.models.media.Schema;
import org.openapitools.codegen.*;
import org.openapitools.codegen.meta.features.DocumentationFeature;
+import org.openapitools.codegen.meta.features.GlobalFeature;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.model.ModelsMap;
import org.openapitools.codegen.model.OperationMap;
@@ -86,7 +87,10 @@ public class TypeScriptAngularClientCodegen extends AbstractTypeScriptClientCode
public TypeScriptAngularClientCodegen() {
super();
- modifyFeatureSet(features -> features.includeDocumentationFeatures(DocumentationFeature.Readme));
+ modifyFeatureSet(features -> features
+ .includeDocumentationFeatures(DocumentationFeature.Readme)
+ .includeGlobalFeatures(GlobalFeature.ParameterStyling)
+ );
this.outputFolder = "generated-code/typescript-angular";
@@ -160,6 +164,7 @@ public class TypeScriptAngularClientCodegen extends AbstractTypeScriptClientCode
supportingFiles.add(new SupportingFile("configuration.mustache", getIndexDirectory(), "configuration.ts"));
supportingFiles.add(new SupportingFile("variables.mustache", getIndexDirectory(), "variables.ts"));
supportingFiles.add(new SupportingFile("encoder.mustache", getIndexDirectory(), "encoder.ts"));
+ supportingFiles.add(new SupportingFile("param.mustache", getIndexDirectory(), "param.ts"));
supportingFiles.add(new SupportingFile("gitignore", "", ".gitignore"));
supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh"));
supportingFiles.add(new SupportingFile("README.mustache", getIndexDirectory(), "README.md"));
@@ -404,6 +409,7 @@ public class TypeScriptAngularClientCodegen extends AbstractTypeScriptClientCode
List ops = objs.getOperation();
boolean hasSomeFormParams = false;
+ boolean hasSomeEncodableParams = false;
for (CodegenOperation op : ops) {
if (op.getHasFormParams()) {
hasSomeFormParams = true;
@@ -413,7 +419,7 @@ public class TypeScriptAngularClientCodegen extends AbstractTypeScriptClientCode
// Prep a string buffer where we're going to set up our new version of the string.
StringBuilder pathBuffer = new StringBuilder();
- StringBuilder parameterName = new StringBuilder();
+ ParameterExpander paramExpander = new ParameterExpander(op, this::toParamName);
int insideCurly = 0;
// Iterate through existing string, one character at a time.
@@ -422,27 +428,18 @@ public class TypeScriptAngularClientCodegen extends AbstractTypeScriptClientCode
case '{':
// We entered curly braces, so track that.
insideCurly++;
-
- // Add the more complicated component instead of just the brace.
- pathBuffer.append("${encodeURIComponent(String(");
break;
case '}':
// We exited curly braces, so track that.
insideCurly--;
- // Add the more complicated component instead of just the brace.
- CodegenParameter parameter = findPathParameterByName(op, parameterName.toString());
- pathBuffer.append(toParamName(parameterName.toString()));
- if (parameter != null && parameter.isDateTime) {
- pathBuffer.append(".toISOString()");
- }
- pathBuffer.append("))}");
- parameterName.setLength(0);
+ pathBuffer.append(paramExpander.buildPathEntry());
+ hasSomeEncodableParams = true;
break;
default:
char nextChar = op.path.charAt(i);
if (insideCurly > 0) {
- parameterName.append(nextChar);
+ paramExpander.appendToParameterName(nextChar);
} else {
pathBuffer.append(nextChar);
}
@@ -455,6 +452,7 @@ public class TypeScriptAngularClientCodegen extends AbstractTypeScriptClientCode
}
operations.put("hasSomeFormParams", hasSomeFormParams);
+ operations.put("hasSomeEncodableParams", hasSomeEncodableParams);
// Add additional filename information for model imports in the services
List