diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs
index d1ad41781..7f1a8ec04 100644
--- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs
+++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiParameterRules.cs
@@ -96,6 +96,22 @@ public static class OpenApiParameterRules
context.Exit();
});
+ ///
+ /// Validate that a path parameter should always appear in the path
+ ///
+ public static ValidationRule PathParameterShouldBeInThePath =>
+ new ValidationRule(
+ (context, parameter) =>
+ {
+ if (parameter.In == ParameterLocation.Path && !context.PathString.Contains("{" + parameter.Name + "}"))
+ {
+ context.Enter("in");
+ context.CreateError(
+ nameof(PathParameterShouldBeInThePath),
+ $"Declared path parameter \"{parameter.Name}\" needs to be defined as a path parameter at either the path or operation level");
+ context.Exit();
+ }
+ });
// add more rule.
}
}
diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
index 9e7af0f05..d5a89e586 100755
--- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
+++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt
@@ -1168,6 +1168,7 @@ namespace Microsoft.OpenApi.Validations.Rules
{
public static Microsoft.OpenApi.Validations.ValidationRule ParameterMismatchedDataType { get; }
public static Microsoft.OpenApi.Validations.ValidationRule ParameterRequiredFields { get; }
+ public static Microsoft.OpenApi.Validations.ValidationRule PathParameterShouldBeInThePath { get; }
public static Microsoft.OpenApi.Validations.ValidationRule RequiredMustBeTrueWhenInIsPath { get; }
}
[Microsoft.OpenApi.Validations.Rules.OpenApiRule]
diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs
index 667286f0a..41a9a6ab0 100644
--- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs
+++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiParameterValidationTests.cs
@@ -48,8 +48,11 @@ public void ValidateRequiredIsTrueWhenInIsPathInParameter()
};
// Act
- var errors = parameter.Validate(ValidationRuleSet.GetDefaultRuleSet());
-
+ var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet());
+ validator.Enter("{name}");
+ var walker = new OpenApiWalker(validator);
+ walker.Walk(parameter);
+ var errors = validator.Errors;
// Assert
errors.Should().NotBeEmpty();
errors.Select(e => e.Message).Should().BeEquivalentTo(new[]
@@ -77,6 +80,7 @@ public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema()
// Act
var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet());
+ validator.Enter("{parameter1}");
var walker = new OpenApiWalker(validator);
walker.Walk(parameter);
@@ -91,7 +95,7 @@ public void ValidateExampleShouldNotHaveDataTypeMismatchForSimpleSchema()
});
errors.Select(e => e.Pointer).Should().BeEquivalentTo(new[]
{
- "#/example",
+ "#/{parameter1}/example",
});
}
@@ -150,6 +154,7 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema()
// Act
var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet());
+ validator.Enter("{parameter1}");
var walker = new OpenApiWalker(validator);
walker.Walk(parameter);
@@ -168,10 +173,83 @@ public void ValidateExamplesShouldNotHaveDataTypeMismatchForSimpleSchema()
{
// #enum/0 is not an error since the spec allows
// representing an object using a string.
- "#/examples/example1/value/y",
- "#/examples/example1/value/z",
- "#/examples/example2/value"
+ "#/{parameter1}/examples/example1/value/y",
+ "#/{parameter1}/examples/example1/value/z",
+ "#/{parameter1}/examples/example2/value"
});
}
+
+ [Fact]
+ public void PathParameterNotInThePathShouldReturnAnError()
+ {
+ // Arrange
+ IEnumerable errors;
+
+ var parameter = new OpenApiParameter()
+ {
+ Name = "parameter1",
+ In = ParameterLocation.Path,
+ Required = true,
+ Schema = new OpenApiSchema()
+ {
+ Type = "string",
+ }
+ };
+
+ // Act
+ var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet());
+
+ var walker = new OpenApiWalker(validator);
+ walker.Walk(parameter);
+
+ errors = validator.Errors;
+ bool result = errors.Any();
+
+ // Assert
+ result.Should().BeTrue();
+ errors.OfType().Select(e => e.RuleName).Should().BeEquivalentTo(new[]
+ {
+ "PathParameterShouldBeInThePath"
+ });
+ errors.Select(e => e.Pointer).Should().BeEquivalentTo(new[]
+ {
+ "#/in"
+ });
+ }
+
+ [Fact]
+ public void PathParameterInThePastShouldBeOk()
+ {
+ // Arrange
+ IEnumerable errors;
+
+ var parameter = new OpenApiParameter()
+ {
+ Name = "parameter1",
+ In = ParameterLocation.Path,
+ Required = true,
+ Schema = new OpenApiSchema()
+ {
+ Type = "string",
+ }
+ };
+
+ // Act
+ var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet());
+ validator.Enter("paths");
+ validator.Enter("/{parameter1}");
+ validator.Enter("get");
+ validator.Enter("parameters");
+ validator.Enter("1");
+
+ var walker = new OpenApiWalker(validator);
+ walker.Walk(parameter);
+
+ errors = validator.Errors;
+ bool result = errors.Any();
+
+ // Assert
+ result.Should().BeFalse();
+ }
}
}
diff --git a/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs
index f300761b4..af259cf1b 100644
--- a/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs
+++ b/test/Microsoft.OpenApi.Tests/Validations/ValidationRuleSetTests.cs
@@ -43,7 +43,7 @@ public void DefaultRuleSetPropertyReturnsTheCorrectRules()
Assert.NotEmpty(rules);
// Update the number if you add new default rule(s).
- Assert.Equal(20, rules.Count);
+ Assert.Equal(21, rules.Count);
}
}
}