diff --git a/src/Microsoft.OpenApi.Tool/OpenApiService.cs b/src/Microsoft.OpenApi.Tool/OpenApiService.cs index 87f02dcce..1c993e0e8 100644 --- a/src/Microsoft.OpenApi.Tool/OpenApiService.cs +++ b/src/Microsoft.OpenApi.Tool/OpenApiService.cs @@ -24,6 +24,7 @@ public static void ProcessOpenApiDocument( OpenApiSpecVersion version, OpenApiFormat format, string filterByOperationIds, + string filterByTags, bool inline, bool resolveExternal) { @@ -52,9 +53,19 @@ public static void ProcessOpenApiDocument( document = result.OpenApiDocument; // Check if filter options are provided, then execute + if (!string.IsNullOrEmpty(filterByOperationIds) && !string.IsNullOrEmpty(filterByTags)) + { + throw new InvalidOperationException("Cannot filter by operationIds and tags at the same time."); + } + if (!string.IsNullOrEmpty(filterByOperationIds)) { - var predicate = OpenApiFilterService.CreatePredicate(filterByOperationIds); + var predicate = OpenApiFilterService.CreatePredicate(operationIds: filterByOperationIds); + document = OpenApiFilterService.CreateFilteredDocument(document, predicate); + } + if (!string.IsNullOrEmpty(filterByTags)) + { + var predicate = OpenApiFilterService.CreatePredicate(tags: filterByTags); document = OpenApiFilterService.CreateFilteredDocument(document, predicate); } diff --git a/src/Microsoft.OpenApi.Tool/Program.cs b/src/Microsoft.OpenApi.Tool/Program.cs index 21be3406b..a4d32c31e 100644 --- a/src/Microsoft.OpenApi.Tool/Program.cs +++ b/src/Microsoft.OpenApi.Tool/Program.cs @@ -29,9 +29,10 @@ static async Task Main(string[] args) new Option("--format", "File format",typeof(OpenApiFormat) ), new Option("--inline", "Inline $ref instances", typeof(bool) ), new Option("--resolveExternal","Resolve external $refs", typeof(bool)), - new Option("--filterByOperationIds", "Filters by OperationId provided", typeof(string)) + new Option("--filterByOperationIds", "Filters OpenApiDocument by OperationId(s) provided", typeof(string)), + new Option("--filterByTags", "Filters OpenApiDocument by Tag(s) provided", typeof(string)) }; - transformCommand.Handler = CommandHandler.Create( + transformCommand.Handler = CommandHandler.Create( OpenApiService.ProcessOpenApiDocument); rootCommand.Add(transformCommand); diff --git a/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs b/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs index 5b5e6f59f..08774995e 100644 --- a/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs +++ b/src/Microsoft.OpenApi/Services/OpenApiFilterService.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using Microsoft.OpenApi.Models; namespace Microsoft.OpenApi.Services @@ -17,10 +18,15 @@ public static class OpenApiFilterService /// Create predicate function based on passed query parameters /// /// Comma delimited list of operationIds or * for all operations. + /// Comma delimited list of tags or a single regex. /// A predicate. - public static Func CreatePredicate(string operationIds) + public static Func CreatePredicate(string operationIds = null, string tags = null) { Func predicate; + if (!string.IsNullOrEmpty(operationIds) && !string.IsNullOrEmpty(tags)) + { + throw new InvalidOperationException("Cannot specify both operationIds and tags at the same time."); + } if (operationIds != null) { if (operationIds == "*") @@ -33,10 +39,23 @@ public static Func CreatePredicate(string operationIds) predicate = (o) => operationIdsArray.Contains(o.OperationId); } } + else if (tags != null) + { + var tagsArray = tags.Split(','); + if (tagsArray.Length == 1) + { + var regex = new Regex(tagsArray[0]); + predicate = (o) => o.Tags.Any(t => regex.IsMatch(t.Name)); + } + else + { + predicate = (o) => o.Tags.Any(t => tagsArray.Contains(t.Name)); + } + } else { - throw new InvalidOperationException("OperationId needs to be specified."); + throw new InvalidOperationException("Either operationId(s) or tag(s) need to be specified."); } return predicate; diff --git a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt index 180a7fd81..0b681a8ec 100755 --- a/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt +++ b/test/Microsoft.OpenApi.Tests/PublicApi/PublicApi.approved.txt @@ -957,7 +957,7 @@ namespace Microsoft.OpenApi.Services public static class OpenApiFilterService { public static Microsoft.OpenApi.Models.OpenApiDocument CreateFilteredDocument(Microsoft.OpenApi.Models.OpenApiDocument source, System.Func predicate) { } - public static System.Func CreatePredicate(string operationIds) { } + public static System.Func CreatePredicate(string operationIds = null, string tags = null) { } } public class OpenApiReferenceError : Microsoft.OpenApi.Models.OpenApiError { diff --git a/test/Microsoft.OpenApi.Tests/Services/OpenApiFilterServiceTests.cs b/test/Microsoft.OpenApi.Tests/Services/OpenApiFilterServiceTests.cs index 308f00952..ab65ed744 100644 --- a/test/Microsoft.OpenApi.Tests/Services/OpenApiFilterServiceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Services/OpenApiFilterServiceTests.cs @@ -19,27 +19,38 @@ public OpenApiFilterServiceTests() } [Theory] - [InlineData("users.user.ListUser")] - [InlineData("users.user.GetUser")] - [InlineData("administrativeUnits.restore")] - [InlineData("graphService.GetGraphService")] - public void ReturnFilteredOpenApiDocumentBasedOnOperationIds(string operationId) + [InlineData("users.user.ListUser", null, 1)] + [InlineData("users.user.GetUser", null, 1)] + [InlineData("users.user.ListUser,users.user.GetUser", null, 2)] + [InlineData("*", null, 12)] + [InlineData("administrativeUnits.restore", null, 1)] + [InlineData("graphService.GetGraphService", null, 1)] + [InlineData(null, "users.user,applications.application", 3)] + [InlineData(null, "^users\\.", 3)] + [InlineData(null, "users.user", 2)] + [InlineData(null, "applications.application", 1)] + [InlineData(null, "reports.Functions", 2)] + public void ReturnFilteredOpenApiDocumentBasedOnOperationIdsAndTags(string operationIds, string tags, int expectedPathCount) { // Act - var predicate = OpenApiFilterService.CreatePredicate(operationId); + var predicate = OpenApiFilterService.CreatePredicate(operationIds, tags); var subsetOpenApiDocument = OpenApiFilterService.CreateFilteredDocument(_openApiDocumentMock, predicate); // Assert Assert.NotNull(subsetOpenApiDocument); - Assert.Single(subsetOpenApiDocument.Paths); + Assert.NotEmpty(subsetOpenApiDocument.Paths); + Assert.Equal(expectedPathCount, subsetOpenApiDocument.Paths.Count); } [Fact] - public void ThrowsInvalidOperationExceptionInCreatePredicateWhenInvalidOperationIdIsSpecified() + public void ThrowsInvalidOperationExceptionInCreatePredicateWhenInvalidArgumentsArePassed() { // Act and Assert - var message = Assert.Throws(() =>OpenApiFilterService.CreatePredicate(null)).Message; - Assert.Equal("OperationId needs to be specified.", message); + var message1 = Assert.Throws(() => OpenApiFilterService.CreatePredicate(null, null)).Message; + Assert.Equal("Either operationId(s) or tag(s) need to be specified.", message1); + + var message2 = Assert.Throws(() => OpenApiFilterService.CreatePredicate("users.user.ListUser", "users.user")).Message; + Assert.Equal("Cannot specify both operationIds and tags at the same time.", message2); } } }