Skip to content

Commit

Permalink
Merge pull request #474 from fabich/feature/add-path-signature-unique…
Browse files Browse the repository at this point in the history
…-validation

[472] Add path signature unique validator
  • Loading branch information
baywet authored Oct 24, 2023
2 parents 8c8c22f + 5140baf commit 7568e30
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 3 deletions.
9 changes: 9 additions & 0 deletions src/Microsoft.OpenApi/Properties/SRResource.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion src/Microsoft.OpenApi/Properties/SRResource.resx
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@
<data name="Validation_PathItemMustBeginWithSlash" xml:space="preserve">
<value>The path item name '{0}' MUST begin with a slash.</value>
</data>
<data name="Validation_PathSignatureMustBeUnique" xml:space="preserve">
<value>The path signature '{0}' MUST be unique.</value>
</data>
<data name="Validation_RuleAddTwice" xml:space="preserve">
<value>The same rule cannot be in the same rule set twice.</value>
</data>
Expand All @@ -222,4 +225,4 @@
<data name="WorkspaceRequredForExternalReferenceResolution" xml:space="preserve">
<value>OpenAPI document must be added to an OpenApiWorkspace to be able to resolve external references.</value>
</data>
</root>
</root>
2 changes: 2 additions & 0 deletions src/Microsoft.OpenApi/Services/OpenApiWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ internal void Walk(OpenApiPaths paths)
_visitor.CurrentKeys.Path = null;
}
}

}

/// <summary>
Expand Down Expand Up @@ -1058,6 +1059,7 @@ internal void Walk(IOpenApiElement element)
case OpenApiOAuthFlow e: Walk(e); break;
case OpenApiOperation e: Walk(e); break;
case OpenApiParameter e: Walk(e); break;
case OpenApiPaths e: Walk(e); break;
case OpenApiRequestBody e: Walk(e); break;
case OpenApiResponse e: Walk(e); break;
case OpenApiSchema e: Walk(e); break;
Expand Down
29 changes: 28 additions & 1 deletion src/Microsoft.OpenApi/Validations/Rules/OpenApiPathsRules.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Properties;

Expand All @@ -23,7 +26,7 @@ public static class OpenApiPathsRules
{
context.Enter(pathName);

if (pathName == null || !pathName.StartsWith("/"))
if (pathName == null || !pathName.StartsWith("/", StringComparison.OrdinalIgnoreCase))
{
context.CreateError(nameof(PathNameMustBeginWithSlash),
string.Format(SRResource.Validation_PathItemMustBeginWithSlash, pathName));
Expand All @@ -33,6 +36,30 @@ public static class OpenApiPathsRules
}
});

private static readonly Regex regexPath = new Regex("\\{([^/]+)\\}", RegexOptions.Compiled, TimeSpan.FromMilliseconds(100));
/// <summary>
/// A relative path to an individual endpoint. The field name MUST begin with a slash.
/// </summary>
public static ValidationRule<OpenApiPaths> PathMustBeUnique =>
new ValidationRule<OpenApiPaths>(
(context, item) =>
{
var hashSet = new HashSet<string>();

foreach (var path in item.Keys)
{
context.Enter(path);

var pathSignature = regexPath.Replace(path, "{}");

if (!hashSet.Add(pathSignature))
context.CreateError(nameof(PathMustBeUnique),
string.Format(SRResource.Validation_PathSignatureMustBeUnique, pathSignature));

context.Exit();
}
});

// add more rules
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1431,6 +1431,7 @@ namespace Microsoft.OpenApi.Validations.Rules
[Microsoft.OpenApi.Validations.Rules.OpenApiRule]
public static class OpenApiPathsRules
{
public static Microsoft.OpenApi.Validations.ValidationRule<Microsoft.OpenApi.Models.OpenApiPaths> PathMustBeUnique { get; }
public static Microsoft.OpenApi.Validations.ValidationRule<Microsoft.OpenApi.Models.OpenApiPaths> PathNameMustBeginWithSlash { get; }
}
[Microsoft.OpenApi.Validations.Rules.OpenApiRule]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.Linq;
using FluentAssertions;
using Microsoft.OpenApi.Extensions;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Properties;
using Xunit;

namespace Microsoft.OpenApi.Validations.Tests
{
public class OpenApiPathsValidationTests
{
[Fact]
public void ValidatePathsMustBeginWithSlash()
{
// Arrange
var error = string.Format(SRResource.Validation_PathItemMustBeginWithSlash, "pets/{petId}");
var paths = new OpenApiPaths
{
{"pets/{petId}",new OpenApiPathItem() }
};

// Act
var errors = paths.Validate(ValidationRuleSet.GetDefaultRuleSet());

// Assert
errors.Should().NotBeEmpty();
errors.Select(e => e.Message).Should().BeEquivalentTo(error);
}

[Fact]
public void ValidatePathsAreUnique()
{
// Arrange
var error = string.Format(SRResource.Validation_PathSignatureMustBeUnique, "/pets/{}");
var paths = new OpenApiPaths
{
{"/pets/{petId}",new OpenApiPathItem() },
{"/pets/{name}",new OpenApiPathItem() }
};

// Act
var errors = paths.Validate(ValidationRuleSet.GetDefaultRuleSet());

// Assert
errors.Should().NotBeEmpty();
errors.Select(e => e.Message).Should().BeEquivalentTo(error);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void DefaultRuleSetPropertyReturnsTheCorrectRules()
Assert.NotEmpty(rules);

// Update the number if you add new default rule(s).
Assert.Equal(22, rules.Count);
Assert.Equal(23, rules.Count);
}
}
}

0 comments on commit 7568e30

Please sign in to comment.