From 5fb847f6c59e44bc4d70c33f89c8ef702586655c Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Fri, 5 Jun 2026 00:07:18 +0200 Subject: [PATCH 1/7] fix: content negotiation order for responses are not defined --- src/OpenAPI.WebApiGenerator/ApiGenerator.cs | 14 ++++---- .../ResponseBodyContentGenerator.cs | 32 +++++++++---------- .../CodeGeneration/ResponseGenerator.cs | 2 +- .../Extensions/MediaTypeExtensions.cs | 1 + tests/Example.OpenApi32/openapi.json | 4 +-- .../Extensions/MediaTypeExtensionsTests.cs | 1 + 6 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/OpenAPI.WebApiGenerator/ApiGenerator.cs b/src/OpenAPI.WebApiGenerator/ApiGenerator.cs index 11bd874..8e4e8e6 100644 --- a/src/OpenAPI.WebApiGenerator/ApiGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/ApiGenerator.cs @@ -169,12 +169,14 @@ private static void GenerateCode(SourceProductionContext context, response.Content?.Where(responseContent => openApiResponseVisitor.HasContent(responseContent.Value)) ?? []; var responseBodyGenerators = responseContent.Select(mediaContent => - { - var contentMediaType = mediaContent.Value; - var contentSchemaReference = openApiResponseVisitor.GetSchemaReference(contentMediaType); - var typeDeclaration = schemaGenerator.Generate(contentSchemaReference); - return new ResponseBodyContentGenerator(mediaContent, typeDeclaration); - }).ToList(); + { + var contentMediaType = mediaContent.Value; + var contentSchemaReference = openApiResponseVisitor.GetSchemaReference(contentMediaType); + var typeDeclaration = schemaGenerator.Generate(contentSchemaReference); + return new ResponseBodyContentGenerator(mediaContent, typeDeclaration); + }) + .OrderByDescending(generator => generator.ContentType.GetPrecedence()) + .ToList(); var responseHeaderGenerators = response.Headers?.Select(valuePair => { diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseBodyContentGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseBodyContentGenerator.cs index 22deb5e..ad32c62 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseBodyContentGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseBodyContentGenerator.cs @@ -12,24 +12,24 @@ internal sealed class ResponseBodyContentGenerator { private readonly string _contentVariableName; internal string ClassName { get; } - private readonly MediaTypeHeaderValue _contentType; + internal MediaTypeHeaderValue ContentType { get; } private readonly TypeDeclaration _typeDeclaration; private readonly bool _isContentTypeRange; private readonly bool _isSequentialMediaType; public ResponseBodyContentGenerator(KeyValuePair contentMediaType, TypeDeclaration typeDeclaration) { - _contentType = MediaTypeHeaderValue.Parse(contentMediaType.Key); + ContentType = MediaTypeHeaderValue.Parse(contentMediaType.Key); _typeDeclaration = typeDeclaration; _isSequentialMediaType = contentMediaType.Value.ItemSchema != null; - _isContentTypeRange = _contentType.MediaType.EndsWith("*"); - _contentVariableName = _contentType.MediaType switch + _isContentTypeRange = ContentType.MediaType.EndsWith("*"); + _contentVariableName = ContentType.MediaType switch { "*/*" => "any", not null when _isContentTypeRange => - $"any{_contentType.MediaType.TrimEnd('*').TrimEnd('/').ToLower().ToPascalCase()}", + $"any{ContentType.MediaType.TrimEnd('*').TrimEnd('/').ToLower().ToPascalCase()}", null => throw new InvalidOperationException("Content type is null"), - _ => _contentType.MediaType.ToLower().ToCamelCase() + _ => ContentType.MediaType.ToLower().ToCamelCase() }; ClassName = _contentVariableName.ToPascalCase(); @@ -40,7 +40,7 @@ public string GenerateResponseClass(string responseClassName, string contentType _isSequentialMediaType ? $$""" /// -/// Response for content {{_contentType}} +/// Response for content {{ContentType}} /// internal sealed class {{ClassName}} : {{responseClassName}} { @@ -51,12 +51,12 @@ internal sealed class {{ClassName}} : {{responseClassName}} private readonly WebApiConfiguration _configuration; /// - /// Construct response for content {{_contentType}} + /// Construct response for content {{ContentType}} /// /// Request{{(_isContentTypeRange ? $""" - /// Content type must match range {_contentType.MediaType} + /// Content type must match range {ContentType.MediaType} """ : "")}} public {{ClassName}}(Request request{{(_isContentTypeRange ? ", string contentType" : "")}}) {{{(_isContentTypeRange ? @@ -68,7 +68,7 @@ internal sealed class {{ClassName}} : {{responseClassName}} _content = new(request.HttpContext.Response.BodyWriter); _operation = request.HttpContext.RequestServices.GetRequiredService(); _configuration = request.HttpContext.RequestServices.GetRequiredService(); - {{contentTypeFieldName}} = {{(_isContentTypeRange ? "contentType" : $"\"{_contentType.MediaType}\"")}}; + {{contentTypeFieldName}} = {{(_isContentTypeRange ? "contentType" : $"\"{ContentType.MediaType}\"")}}; } /// @@ -85,7 +85,7 @@ internal void WriteItem({{_typeDeclaration.FullyQualifiedDotnetTypeName()}} item _currentItem = null; } - internal static ContentMediaType<{{responseClassName}}> ContentMediaType { get; } = new(MediaTypeHeaderValue.Parse("{{_contentType}}")); + internal static ContentMediaType<{{responseClassName}}> ContentMediaType { get; } = new(MediaTypeHeaderValue.Parse("{{ContentType}}")); /// internal override void WriteTo(HttpResponse httpResponse) { @@ -127,19 +127,19 @@ _currentItem is null $$""" /// -/// Response for content {{_contentType}} +/// Response for content {{ContentType}} /// internal sealed class {{ClassName}} : {{responseClassName}} { private {{_typeDeclaration.FullyQualifiedDotnetTypeName()}} _content; /// - /// Construct response for content {{_contentType}} + /// Construct response for content {{ContentType}} /// /// Content{{(_isContentTypeRange ? $""" - /// Content type must match range {_contentType.MediaType} + /// Content type must match range {ContentType.MediaType} """ : "")}} public {{ClassName}}({{_typeDeclaration.FullyQualifiedDotnetTypeName()}} {{_contentVariableName}}{{(_isContentTypeRange ? ", string contentType" : "")}}) {{{(_isContentTypeRange ? @@ -148,10 +148,10 @@ internal sealed class {{ClassName}} : {{responseClassName}} EnsureExpectedContentType(MediaTypeHeaderValue.Parse(contentType), ContentMediaType); """ : "")}} _content = {{_contentVariableName}}; - {{contentTypeFieldName}} = {{(_isContentTypeRange ? "contentType" : $"\"{_contentType.MediaType}\"")}}; + {{contentTypeFieldName}} = {{(_isContentTypeRange ? "contentType" : $"\"{ContentType.MediaType}\"")}}; } - internal static ContentMediaType<{{responseClassName}}> ContentMediaType { get; } = new(MediaTypeHeaderValue.Parse("{{_contentType}}")); + internal static ContentMediaType<{{responseClassName}}> ContentMediaType { get; } = new(MediaTypeHeaderValue.Parse("{{ContentType}}")); /// internal override void WriteTo(HttpResponse httpResponse) { diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseGenerator.cs index 088a9c3..836f1b6 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseGenerator.cs @@ -125,7 +125,7 @@ internal bool TryMatchAcceptMediaType( foreach (var mediaType in mediaTypes) { - if (!mediaType.Value.IsSubsetOf(acceptMediaType)) + if (!acceptMediaType.IsSubsetOf(mediaType.Value)) { continue; } diff --git a/src/OpenAPI.WebApiGenerator/Extensions/MediaTypeExtensions.cs b/src/OpenAPI.WebApiGenerator/Extensions/MediaTypeExtensions.cs index a4a301c..b86d5a8 100644 --- a/src/OpenAPI.WebApiGenerator/Extensions/MediaTypeExtensions.cs +++ b/src/OpenAPI.WebApiGenerator/Extensions/MediaTypeExtensions.cs @@ -34,6 +34,7 @@ internal static int GetPrecedence(this MediaTypeHeaderValue value) => { "*/*" => 0, not null when value.MediaType.EndsWith("*") => 100, + not null when value.MediaType.Contains("+") => 2000, _ => 1000 }; } \ No newline at end of file diff --git a/tests/Example.OpenApi32/openapi.json b/tests/Example.OpenApi32/openapi.json index 5d00075..1f59ed0 100644 --- a/tests/Example.OpenApi32/openapi.json +++ b/tests/Example.OpenApi32/openapi.json @@ -137,12 +137,12 @@ "$ref": "#/components/schemas/FooProperties" } }, - "application/json-seq": { + "application/geo+json-seq": { "itemSchema": { "$ref": "#/components/schemas/FooProperties" } }, - "application/geo+json-seq": { + "application/json-seq": { "itemSchema": { "$ref": "#/components/schemas/FooProperties" } diff --git a/tests/OpenAPI.WebApiGenerator.UnitTests/Extensions/MediaTypeExtensionsTests.cs b/tests/OpenAPI.WebApiGenerator.UnitTests/Extensions/MediaTypeExtensionsTests.cs index f123f42..d2bb6e3 100644 --- a/tests/OpenAPI.WebApiGenerator.UnitTests/Extensions/MediaTypeExtensionsTests.cs +++ b/tests/OpenAPI.WebApiGenerator.UnitTests/Extensions/MediaTypeExtensionsTests.cs @@ -31,6 +31,7 @@ public void MediaTypeHeaderValue_MatchConditionExpressions(string mediaTypeValue [InlineData("application/*; charset=utf-8; boundary=something", 102)] [InlineData("application/*; charset=utf-8; boundary=something; foo=bar", 103)] [InlineData("application/json", 1000)] + [InlineData("application/geo+json", 2000)] [InlineData("application/json; charset=utf-8", 1001)] [InlineData("application/json; charset=utf-8; boundary=something", 1002)] [InlineData("multipart/form-data; boundary", 1001)] From db67d2ba6e009e821759b051e6f9812a9fbdc56d Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Fri, 5 Jun 2026 00:09:11 +0200 Subject: [PATCH 2/7] fix: proper sub media type precedence --- .../HttpRequestExtensionsGenerator.cs | 7 ++++--- .../CodeGeneration/ResponseContentGenerator.cs | 16 +++++++++++----- .../RequestBodyContentGeneratorExtensionTests.cs | 2 ++ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/HttpRequestExtensionsGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/HttpRequestExtensionsGenerator.cs index 9bd62de..aeead94 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/HttpRequestExtensionsGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/HttpRequestExtensionsGenerator.cs @@ -168,9 +168,10 @@ private static bool TryParse(StringValues values, IParameter parameter, [NotN } var parser = GetParser(parameter); - var stringValue = parser.ValueIncludesParameterName - ? string.Join('&', values.Select(value => $"{parameter.Name}={value}")) - : values.Single(); + var stringValue = string.Join(parser.Delimiter, + parser.ValueIncludesParameterName + ? values.Select(value => $"{parameter.Name}={value}") + : values); value = Parse(parser, stringValue); return true; diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseContentGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseContentGenerator.cs index ce9ca2f..a480882 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseContentGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseContentGenerator.cs @@ -14,6 +14,8 @@ internal sealed class ResponseContentGenerator private readonly string _responseClassName; private readonly string _responseStatusCodePattern; private readonly IOpenApiResponse _response; + private readonly bool _hasExplicitStatusCode; + private readonly bool _hasDefaultStatusCode; private ResponseContentGenerator( KeyValuePair response) @@ -36,6 +38,10 @@ var chr when char.IsDigit(chr) => "X", _responseStatusCodePattern = responseStatusCodePattern; _responseClassName = responseClassName; _response = response.Value; + _hasExplicitStatusCode = int.TryParse(_responseStatusCodePattern, out _); + _hasDefaultStatusCode = _responseStatusCodePattern == "default"; + Precedence = _hasExplicitStatusCode ? 0 : _hasDefaultStatusCode ? 10 : 5; + } public ResponseContentGenerator( KeyValuePair response, @@ -46,6 +52,8 @@ public ResponseContentGenerator( _headerGenerators = headerGenerators; } + internal int Precedence { get; } + public string GenerateResponseContentClass() { var anyHeaders = _headerGenerators.Any(); @@ -55,9 +63,7 @@ public string GenerateResponseContentClass() const string responseVariableName = "httpResponse"; const string contentTypeFieldName = "_contentType"; - var hasExplicitStatusCode = int.TryParse(_responseStatusCodePattern, out _); - var hasDefaultStatusCode = _responseStatusCodePattern == "default"; - var needsStatusCodeValidation = !hasExplicitStatusCode && !hasDefaultStatusCode; + var needsStatusCodeValidation = !_hasExplicitStatusCode && !_hasDefaultStatusCode; return $$$""" @@ -79,13 +85,13 @@ public string GenerateResponseContentClass() ]; """ : "")}}} - private int _statusCode{{{(hasExplicitStatusCode ? $" = {_responseStatusCodePattern}" : string.Empty)}}}; + private int _statusCode{{{(_hasExplicitStatusCode ? $" = {_responseStatusCodePattern}" : string.Empty)}}}; /// /// Response status code /// internal int StatusCode { - get => _statusCode;{{{(hasExplicitStatusCode ? "" : + get => _statusCode;{{{(_hasExplicitStatusCode ? "" : $""" init => _statusCode = {(needsStatusCodeValidation ? $"Validate{_responseStatusCodePattern.First()}xxStatusCode(value)" : "value")}; """)}}} diff --git a/tests/OpenAPI.WebApiGenerator.UnitTests/CodeGeneration/RequestBodyContentGeneratorExtensionTests.cs b/tests/OpenAPI.WebApiGenerator.UnitTests/CodeGeneration/RequestBodyContentGeneratorExtensionTests.cs index 97ddce2..6acd690 100644 --- a/tests/OpenAPI.WebApiGenerator.UnitTests/CodeGeneration/RequestBodyContentGeneratorExtensionTests.cs +++ b/tests/OpenAPI.WebApiGenerator.UnitTests/CodeGeneration/RequestBodyContentGeneratorExtensionTests.cs @@ -17,6 +17,7 @@ public void ListOfRequestBodyContentGenerators_SortByContentType_SortsAccordingT CreateGenerator("*/*"), CreateGenerator("application/json; q=0.5"), CreateGenerator("text/*"), + CreateGenerator("application/geo+json"), CreateGenerator("application/json"), CreateGenerator("text/*; q=0.9"), CreateGenerator("application/json; charset=utf-8"), @@ -28,6 +29,7 @@ public void ListOfRequestBodyContentGenerators_SortByContentType_SortsAccordingT .ToArray(); sorted.Should().ContainInOrder( + "application/geo+json", "application/json; charset=utf-8", "application/json", "text/plain", From c3e3a25d7d3af3ba0a530fbc3185b0a64fbd006d Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Fri, 5 Jun 2026 00:10:33 +0200 Subject: [PATCH 3/7] use latest parameter style parser package for the examples --- tests/Example.OpenApi20/Example.OpenApi20.csproj | 2 +- tests/Example.OpenApi30/Example.OpenApi30.csproj | 2 +- tests/Example.OpenApi31/Example.OpenApi31.csproj | 2 +- tests/Example.OpenApi32/Example.OpenApi32.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Example.OpenApi20/Example.OpenApi20.csproj b/tests/Example.OpenApi20/Example.OpenApi20.csproj index 46a091d..f73c2e0 100644 --- a/tests/Example.OpenApi20/Example.OpenApi20.csproj +++ b/tests/Example.OpenApi20/Example.OpenApi20.csproj @@ -16,7 +16,7 @@ - + diff --git a/tests/Example.OpenApi30/Example.OpenApi30.csproj b/tests/Example.OpenApi30/Example.OpenApi30.csproj index 5d3b717..d38bf49 100644 --- a/tests/Example.OpenApi30/Example.OpenApi30.csproj +++ b/tests/Example.OpenApi30/Example.OpenApi30.csproj @@ -17,7 +17,7 @@ - + diff --git a/tests/Example.OpenApi31/Example.OpenApi31.csproj b/tests/Example.OpenApi31/Example.OpenApi31.csproj index 93cacae..34288c8 100644 --- a/tests/Example.OpenApi31/Example.OpenApi31.csproj +++ b/tests/Example.OpenApi31/Example.OpenApi31.csproj @@ -18,7 +18,7 @@ - + diff --git a/tests/Example.OpenApi32/Example.OpenApi32.csproj b/tests/Example.OpenApi32/Example.OpenApi32.csproj index 227883b..feb3185 100644 --- a/tests/Example.OpenApi32/Example.OpenApi32.csproj +++ b/tests/Example.OpenApi32/Example.OpenApi32.csproj @@ -18,7 +18,7 @@ - + From c4c0ab9a0bc9ccf2da73787ed89a8c823085d353 Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Fri, 5 Jun 2026 17:57:23 +0200 Subject: [PATCH 4/7] fix(response): match accept ranges according to precedence --- .../CodeGeneration/ResponseGenerator.cs | 25 +++++++++++---- .../ExportFooEventsTests.cs | 32 +++++++++++++++++++ 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseGenerator.cs b/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseGenerator.cs index 836f1b6..984e90e 100644 --- a/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseGenerator.cs +++ b/src/OpenAPI.WebApiGenerator/CodeGeneration/ResponseGenerator.cs @@ -123,13 +123,26 @@ internal bool TryMatchAcceptMediaType( if ((acceptMediaType.Quality ?? 1.0) <= 0) continue; - foreach (var mediaType in mediaTypes) + // Exact match + var match = mediaTypes.FirstOrDefault(mediaType => + acceptMediaType.IsSubsetOf(mediaType.Value) && + mediaType.Value.IsSubsetOf(acceptMediaType)); + + // Accept media type is broader than a supported media type; + // */*, application/*, application/*+json -> matches Accept header */* + if (match.Value is null) + match = mediaTypes.FirstOrDefault(mediaType => + mediaType.Value.IsSubsetOf(acceptMediaType)); + + // Accept media type fits within a broader supported media type; + // Accept header application/json matches -> */*, application/* + if (match.Value is null) + match = mediaTypes.FirstOrDefault(mediaType => + acceptMediaType.IsSubsetOf(mediaType.Value)); + + if (match.Value is not null) { - if (!acceptMediaType.IsSubsetOf(mediaType.Value)) - { - continue; - } - matchedContentMediaType = mediaType; + matchedContentMediaType = match; return true; } } diff --git a/tests/Example.OpenApi32.IntegrationTests/ExportFooEventsTests.cs b/tests/Example.OpenApi32.IntegrationTests/ExportFooEventsTests.cs index d51216c..15e2f97 100644 --- a/tests/Example.OpenApi32.IntegrationTests/ExportFooEventsTests.cs +++ b/tests/Example.OpenApi32.IntegrationTests/ExportFooEventsTests.cs @@ -38,4 +38,36 @@ public async Task ExportingFooEvents_ShouldReturnOkWithSequentialJson(string med JsonNode.Parse(lines[0]).GetValue("#/Name").Should().Be("foo1"); JsonNode.Parse(lines[1]).GetValue("#/Name").Should().Be("foo2"); } + + [Theory] + // Media type ranges match any supported media type; the most specific one is preferred + [InlineData("*/*", "application/geo+json-seq")] + [InlineData("application/*", "application/geo+json-seq")] + // A specific accepted media type is preferred over a range with the same quality + [InlineData("application/*, application/x-ndjson", "application/x-ndjson")] + // Higher quality wins over declaration order + [InlineData("application/jsonl;q=0.5, application/x-ndjson", "application/x-ndjson")] + [InlineData("application/jsonl, application/x-ndjson;q=0.5", "application/jsonl")] + // q=0 means not acceptable + [InlineData("application/json-seq;q=0, application/x-ndjson;q=0.5", "application/x-ndjson")] + // An exactly supported media type is preferred over a suffixed specialization of it + [InlineData("application/json-seq", "application/json-seq")] + // A suffix media type range prefers the most specific supported media type + [InlineData("application/*+json-seq", "application/geo+json-seq")] + public async Task ExportingFooEvents_NegotiatingAcceptedMediaType_ShouldReturnBestMatch( + string acceptHeader, string expectedMediaType) + { + using var client = app.CreateClient(); + var request = new HttpRequestMessage + { + RequestUri = new Uri(client.BaseAddress!, "/foo/1/events"), + Method = HttpMethod.Get + }; + request.Headers.TryAddWithoutValidation("Accept", acceptHeader); + + var result = await client.SendAsync(request, CancellationToken); + + result.StatusCode.Should().Be(HttpStatusCode.OK); + result.Content.Headers.ContentType?.MediaType.Should().Be(expectedMediaType); + } } \ No newline at end of file From 29277c9d1c7dc77cbf6ccc6375ef280743b3a757 Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Fri, 5 Jun 2026 18:12:42 +0200 Subject: [PATCH 5/7] ci: upgrade actions --- .github/workflows/cd.yml | 6 +++--- .github/workflows/lint-openapi.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 0e3a1f6..3bd5bd4 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -22,10 +22,10 @@ jobs: matrix: os: [ubuntu-latest] steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Setup .NET id: dotnet - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: ${{ env.dotnet_version }} - name: Build @@ -40,7 +40,7 @@ jobs: release: name: Create Release - uses: Fresa/Library.Net.ContinuousDelivery/.github/workflows/release.yml@v0 + uses: Fresa/Library.Net.ContinuousDelivery/.github/workflows/release.yml@v1 needs: test if: github.repository == needs.test.outputs.repository && github.actor != 'dependabot[bot]' permissions: diff --git a/.github/workflows/lint-openapi.yml b/.github/workflows/lint-openapi.yml index 4fedb20..35a1f9c 100644 --- a/.github/workflows/lint-openapi.yml +++ b/.github/workflows/lint-openapi.yml @@ -11,7 +11,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Install vacuum run: curl -fsSL https://quobix.com/scripts/install_vacuum.sh | sh From 6bbc4b3e0448501cd788bada2af3a331d977ecf9 Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Fri, 5 Jun 2026 18:41:48 +0200 Subject: [PATCH 6/7] ci(lint): ignore null glob matches --- .github/workflows/lint-openapi.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint-openapi.yml b/.github/workflows/lint-openapi.yml index 35a1f9c..91c013f 100644 --- a/.github/workflows/lint-openapi.yml +++ b/.github/workflows/lint-openapi.yml @@ -17,4 +17,6 @@ jobs: run: curl -fsSL https://quobix.com/scripts/install_vacuum.sh | sh - name: Lint OpenAPI specs - run: vacuum lint tests/{*/,*/*/}openapi*.{json,yaml} + run: | + shopt -s nullglob + vacuum lint tests/{*/,*/*/}openapi*.{json,yaml} From 8e0fb11b8c4c03f4c529b48a01ea90d9e0e9601c Mon Sep 17 00:00:00 2001 From: Fredrik Arvidsson Date: Fri, 5 Jun 2026 18:45:31 +0200 Subject: [PATCH 7/7] ci: pin vacuum version --- .github/workflows/lint-openapi.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-openapi.yml b/.github/workflows/lint-openapi.yml index 91c013f..207ac7d 100644 --- a/.github/workflows/lint-openapi.yml +++ b/.github/workflows/lint-openapi.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v6 - name: Install vacuum - run: curl -fsSL https://quobix.com/scripts/install_vacuum.sh | sh + run: curl -fsSL https://quobix.com/scripts/install_vacuum.sh | VERSION=0.29.1 sh - name: Lint OpenAPI specs run: |