diff --git a/.gitignore b/.gitignore
index ae82a3cb09..dcfeddf2d5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+openapi
+
src/**/bin/**
src/**/obj/**
src/packages/**
diff --git a/src/NSwag.CodeGeneration.CSharp.Tests/PathPatternValidationTests.cs b/src/NSwag.CodeGeneration.CSharp.Tests/PathPatternValidationTests.cs
new file mode 100644
index 0000000000..af207a44cb
--- /dev/null
+++ b/src/NSwag.CodeGeneration.CSharp.Tests/PathPatternValidationTests.cs
@@ -0,0 +1,180 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Xunit;
+using System.IO;
+
+
+namespace NSwag.CodeGeneration.CSharp.Tests
+{
+ public class PathPatternValidationTests
+ {
+ private static string GenerateSpec(bool withPattern)
+ {
+ return $@"{{
+ ""openapi"": ""3.0.1"",
+ ""info"": {{
+ ""title"": ""NSwager Test Server API"",
+ ""description"": ""An api used to test NSwager."",
+ ""version"": ""2.0""
+ }},
+ ""paths"": {{
+ ""/api/v2/test/{{PathVariable}}/ping"": {{
+ ""get"": {{
+ ""tags"": [
+ ""TestControllerVersionTwo""
+ ],
+ ""summary"": ""Used to ping a valid user name."",
+ ""operationId"": ""PingpathVariableV2"",
+ ""parameters"": [
+ {{
+ ""name"": ""pathVariable"",
+ ""in"": ""path"",
+ ""description"": ""A System.String"",
+ ""required"": true,
+ ""schema"": {{
+ {(withPattern ? @"""pattern"": ""^[a-zA-Z0-9_]+$"", " : "")}
+ ""type"": ""string""
+ }}
+ }}
+ ],
+ ""responses"": {{
+ ""200"": {{
+ ""description"": ""With the user name"",
+ ""content"": {{
+ ""application/json"": {{
+ ""schema"": {{
+ ""$ref"": ""#/components/schemas/PathVariableDTO""
+ }}
+ }}
+ }}
+ }}
+ }}
+ }}
+ }}
+ }},
+ ""components"": {{
+ ""schemas"": {{
+ ""PathVariableDTO"": {{
+ ""required"": [
+ ""pathVariable""
+ ],
+ ""type"": ""object"",
+ ""properties"": {{
+ ""pathVariable"": {{
+ ""type"": ""string"",
+ ""description"": ""The value of the path variable"",
+ ""nullable"": true
+ }}
+ }},
+ ""additionalProperties"": false,
+ ""description"": ""A DTO containing a path variable""
+ }}
+ }}
+ }}
+}}";
+ }
+
+ ///
+ /// This string if statement is exactly the same as the if statement in method.
+ ///
+ private string generatedCode =
+@"if (!System.Text.RegularExpressions.Regex.IsMatch(pathVariable, ""^[a-zA-Z0-9_]+$""))
+ throw new System.ArgumentException(""Parameter 'pathVariable' does not match the required pattern '^[a-zA-Z0-9_]+$'."");";
+
+ ///
+ /// Used to mock execution of the generated code
+ ///
+ /// The value of the path variable.
+ /// A regular expression use to validate .
+ /// Thrown if
+ /// does not match the .
+ private static void ValidatePathPatternMock(string pathVariable, string regexPattern)
+ {
+ if (!System.Text.RegularExpressions.Regex.IsMatch(pathVariable, regexPattern))
+ throw new System.ArgumentException(
+ $"Parameter 'pathVariable' does not match the required pattern '{regexPattern}'."
+ );
+ }
+
+ [Fact]
+ public async Task When_path_parameter_have_pattern_field()
+ {
+ // Arrange
+ var seetings = new CSharpClientGeneratorSettings();
+ var document = await OpenApiDocument.FromJsonAsync(GenerateSpec(withPattern: true));
+ var generator = new CSharpClientGenerator(document, seetings);
+
+ // Act
+ var code = generator.GenerateFile();
+
+ // Assert
+ Assert.Contains(NormalizeWhitespace(generatedCode), NormalizeWhitespace(code));
+ }
+
+ [Fact]
+ public async Task When_path_parameter_not_have_pattern_field()
+ {
+ // Arrange
+ var seetings = new CSharpClientGeneratorSettings();
+ var document = await OpenApiDocument.FromJsonAsync(GenerateSpec(withPattern: false));
+ var generator = new CSharpClientGenerator(document, seetings);
+
+ // Act
+ var code = generator.GenerateFile();
+
+ // Assert
+ Assert.DoesNotContain(generatedCode, code);
+ }
+
+ [Theory]
+ [InlineData("MockValue123", "^[a-zA-Z0-9_]+$")] // Alphanumeric and underscores
+ [InlineData("MockValue-123", "^[a-zA-Z0-9_-]+$")] // Alphanumeric, underscores, and dashes
+ [InlineData("Mock.Value.123", "^[a-zA-Z0-9._]+$")] // Alphanumeric, dots, and underscores
+ [InlineData("123456", "^\\d+$")] // Digits only
+ public void ValidatePathVariable_ValidPathVariables_DoesNotThrow(
+ string pathVariable,
+ string regexPattern
+ )
+ {
+ // Arrange & Act & Assert
+ var exception = Record.Exception(
+ () => ValidatePathPatternMock(pathVariable, regexPattern)
+ );
+ Assert.Null(exception);
+ }
+
+ [Theory]
+ [InlineData("Mock@123", "^[a-zA-Z0-9_]+$")] // Alphanumeric and underscores
+ [InlineData("Mock Value", "^[a-zA-Z0-9_-]+$")] // Alphanumeric, underscores, and dashes
+ [InlineData("Mock!Value", "^[a-zA-Z0-9._]+$")] // Alphanumeric, dots, and underscores
+ [InlineData("MockValue123", "^\\d+$")] // Digits only
+ public void ValidatePathVariable_InvalidPathVariables_ThrowsArgumentException(
+ string pathVariable,
+ string regexPattern
+ )
+ {
+ // Arrange & Act & Assert
+ var exception = Assert.Throws(
+ () => ValidatePathPatternMock(pathVariable, regexPattern)
+ );
+ Assert.Contains(
+ $"Parameter 'pathVariable' does not match the required pattern",
+ exception.Message
+ );
+ }
+
+ private static string NormalizeWhitespace(string input)
+ {
+ char[] separators = ['\r', '\n', '\t', ' '];
+ return string.Join(
+ " ",
+ input.Split(separators, StringSplitOptions.RemoveEmptyEntries)
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/NSwag.CodeGeneration.CSharp/Templates/Client.Class.liquid b/src/NSwag.CodeGeneration.CSharp/Templates/Client.Class.liquid
index 52400fa632..05430fc5e3 100644
--- a/src/NSwag.CodeGeneration.CSharp/Templates/Client.Class.liquid
+++ b/src/NSwag.CodeGeneration.CSharp/Templates/Client.Class.liquid
@@ -156,6 +156,11 @@
if ({{ parameter.VariableName }} == null)
throw new System.ArgumentNullException("{{ parameter.VariableName }}");
+{% endif -%}
+{% if parameter.Schema.Pattern -%}
+ if (!System.Text.RegularExpressions.Regex.IsMatch({{ parameter.VariableName }}, "{{ parameter.Schema.Pattern }}"))
+ throw new System.ArgumentException("Parameter '{{ parameter.VariableName }}' does not match the required pattern '{{ parameter.Schema.Pattern }}'.");
+
{% endif -%}
{% endfor -%}
{% for parameter in operation.QueryParameters -%}
@@ -479,4 +484,4 @@
{% template Client.Class.ConvertToString %}
{% template Client.Class.Body %}
-}
+}
\ No newline at end of file
diff --git a/src/NSwag.Npm/bin/nswag.js b/src/NSwag.Npm/bin/nswag.js
deleted file mode 100644
index c1446d91fe..0000000000
--- a/src/NSwag.Npm/bin/nswag.js
+++ /dev/null
@@ -1,71 +0,0 @@
-#!/usr/bin/env node
-"use strict";
-
-var defaultCoreVersion = "Net80";
-var supportedCoreVersions = [
- { ver: '8.0', dir: "Net80", },
- { ver: '9.0', dir: "Net90", },
-];
-
-// Initialize
-process.title = 'nswag';
-console.log("NSwag NPM CLI");
-var args = process.argv.splice(2, process.argv.length - 2).map(function (a) { return a.indexOf(" ") === -1 ? a : '"' + a + '"' }).join(" ");
-
-// Legacy support
-args = args.replace("--x86", "/runtime:WinX86");
-args = args.replace("/runtime:x86", "/runtime:WinX86");
-args = args.replace("--core 8.0", "/runtime:Net80");
-args = args.replace("--core 9.0", "/runtime:Net90");
-args = args.replace("--core", "/runtime:" + defaultCoreVersion);
-
-// Search for full .NET installation
-var hasFullDotNet = false;
-var fs = require('fs');
-if (process.env["windir"]) {
- try {
- var stats = fs.lstatSync(process.env["windir"] + '/Microsoft.NET');
- if (stats.isDirectory())
- hasFullDotNet = true;
- }
- catch (e) {
- console.log(e);
- }
-}
-
-var c = require('child_process');
-if (hasFullDotNet && args.toLowerCase().indexOf("/runtime:win") != -1) {
- // Run full .NET version
- if (args.toLowerCase().indexOf("/runtime:winx86") != -1) {
- var cmd = '"' + __dirname + '/binaries/Win/nswag.x86.exe" ' + args;
- var code = c.execSync(cmd, { stdio: [0, 1, 2] });
- } else {
- var cmd = '"' + __dirname + '/binaries/Win/nswag.exe" ' + args;
- var code = c.execSync(cmd, { stdio: [0, 1, 2] });
- }
-} else {
- // Run .NET Core version
- var defaultCmd = 'dotnet "' + __dirname + '/binaries/' + defaultCoreVersion + '/dotnet-nswag.dll" ' + args;
- var infoCmd = "dotnet --version";
- c.exec(infoCmd, (error, stdout, _stderr) => {
- for (let version of supportedCoreVersions) {
- var coreCmd = 'dotnet "' + __dirname + '/binaries/' + version.dir + '/dotnet-nswag.dll" ' + args;
-
- if (args.toLowerCase().indexOf("/runtime:" + version.dir.toLocaleLowerCase()) != -1) {
- c.execSync(coreCmd, { stdio: [0, 1, 2] });
- return;
- } else {
- if (!error) {
- var coreVersion = stdout;
-
- if (coreVersion.startsWith(version.ver)) {
- c.execSync(coreCmd, { stdio: [0, 1, 2] });
- return;
- }
- }
- }
- }
- c.execSync(defaultCmd, { stdio: [0, 1, 2] });
- return;
- });
-}
diff --git a/src/NSwag.Npm/package-lock.json b/src/NSwag.Npm/package-lock.json
index 08a92acf45..9a1b8ee9d7 100644
--- a/src/NSwag.Npm/package-lock.json
+++ b/src/NSwag.Npm/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "nswag",
- "version": "14.0.0",
+ "version": "14.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "nswag",
- "version": "14.0.0",
+ "version": "14.2.0",
"license": "MIT",
"bin": {
"nswag": "bin/nswag.js"