Skip to content

Commit e23fd04

Browse files
authored
Handle shortened JSON file in dotnet openapi (#36171)
* Handle shortened JSON file in `dotnet openapi` - #35767 - an existing JSON file must be truncated * !fixup! Address nits in changed file - take VS suggestions * Stop skipping `dotnet openapi` tests - .NET SDKs and Visual Studio's `msbuild` avoid #32686 already * !fixup! Address nits in `dotnet openapi` test files - take VS suggestions * Extend `dotnet openapi refresh` tests - include regression test for #35767
1 parent 254e105 commit e23fd04

File tree

6 files changed

+124
-110
lines changed

6 files changed

+124
-110
lines changed

src/Tools/Microsoft.dotnet-openapi/src/Commands/BaseCommand.cs

Lines changed: 26 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
5-
using System.Collections.Generic;
64
using System.Diagnostics;
7-
using System.IO;
85
using System.Linq;
96
using System.Net;
107
using System.Net.Http;
118
using System.Reflection;
129
using System.Security.Cryptography;
1310
using System.Text.Json;
14-
using System.Threading;
15-
using System.Threading.Tasks;
1611
using Microsoft.Build.Evaluation;
1712
using Microsoft.DotNet.Openapi.Tools;
1813
using Microsoft.DotNet.Openapi.Tools.Internal;
@@ -46,9 +41,9 @@ public BaseCommand(CommandLineApplication parent, string name, IHttpClientWrappe
4641

4742
ProjectFileOption = Option("-p|--updateProject", "The project file update.", CommandOptionType.SingleValue);
4843

49-
if (Parent is Application)
44+
if (Parent is Application application)
5045
{
51-
WorkingDirectory = ((Application)Parent).WorkingDirectory;
46+
WorkingDirectory = application.WorkingDirectory;
5247
}
5348
else
5449
{
@@ -89,10 +84,11 @@ private async Task<int> ExecuteAsync()
8984
private Application GetApplication()
9085
{
9186
var parent = Parent;
92-
while(!(parent is Application))
87+
while(parent is not Application)
9388
{
9489
parent = parent.Parent;
9590
}
91+
9692
return (Application)parent;
9793
}
9894

@@ -126,7 +122,7 @@ internal FileInfo ResolveProjectFile(CommandOption projectOption)
126122
return new FileInfo(project);
127123
}
128124

129-
protected Project LoadProject(FileInfo projectFile)
125+
protected static Project LoadProject(FileInfo projectFile)
130126
{
131127
var project = ProjectCollection.GlobalProjectCollection.LoadProject(
132128
projectFile.FullName,
@@ -136,12 +132,12 @@ protected Project LoadProject(FileInfo projectFile)
136132
return project;
137133
}
138134

139-
internal bool IsProjectFile(string file)
135+
internal static bool IsProjectFile(string file)
140136
{
141137
return File.Exists(Path.GetFullPath(file)) && file.EndsWith(".csproj", StringComparison.Ordinal);
142138
}
143139

144-
internal bool IsUrl(string file)
140+
internal static bool IsUrl(string file)
145141
{
146142
return Uri.TryCreate(file, UriKind.Absolute, out var _) && file.StartsWith("http", StringComparison.Ordinal);
147143
}
@@ -167,7 +163,8 @@ internal async Task AddOpenAPIReference(
167163

168164
if (sourceUrl != null)
169165
{
170-
if (items.Any(i => string.Equals(i.GetMetadataValue(SourceUrlAttrName), sourceUrl)))
166+
if (items.Any(
167+
i => string.Equals(i.GetMetadataValue(SourceUrlAttrName), sourceUrl, StringComparison.Ordinal)))
171168
{
172169
Warning.Write($"A reference to '{sourceUrl}' already exists in '{project.FullPath}'.");
173170
return;
@@ -299,8 +296,8 @@ internal async Task<string> DownloadGivenOption(string url, CommandOption fileOp
299296
/// <param name="retryCount"></param>
300297
private static async Task<IHttpResponseMessageWrapper> RetryRequest(
301298
Func<Task<IHttpResponseMessageWrapper>> retryBlock,
302-
CancellationToken cancellationToken = default,
303-
int retryCount = 60)
299+
int retryCount = 60,
300+
CancellationToken cancellationToken = default)
304301
{
305302
for (var retry = 0; retry < retryCount; retry++)
306303
{
@@ -331,7 +328,7 @@ private static async Task<IHttpResponseMessageWrapper> RetryRequest(
331328
{
332329
if (exception is HttpRequestException || exception is WebException)
333330
{
334-
await Task.Delay(1 * 1000); //Wait for a while before retry.
331+
await Task.Delay(1 * 1000, cancellationToken); // Wait for a while before retry.
335332
}
336333
}
337334
}
@@ -340,7 +337,7 @@ private static async Task<IHttpResponseMessageWrapper> RetryRequest(
340337
throw new OperationCanceledException("Failed to connect, retry limit exceeded.");
341338
}
342339

343-
private string GetUniqueFileName(string directory, string fileName, string extension)
340+
private static string GetUniqueFileName(string directory, string fileName, string extension)
344341
{
345342
var uniqueName = fileName;
346343

@@ -366,7 +363,7 @@ private string GetUniqueFileName(string directory, string fileName, string exten
366363
return uniqueName + extension;
367364
}
368365

369-
private string GetFileNameFromResponse(IHttpResponseMessageWrapper response, string url)
366+
private static string GetFileNameFromResponse(IHttpResponseMessageWrapper response, string url)
370367
{
371368
var contentDisposition = response.ContentDisposition();
372369
string result;
@@ -396,22 +393,12 @@ private string GetFileNameFromResponse(IHttpResponseMessageWrapper response, str
396393
else
397394
{
398395
var parts = uri.Host.Split('.');
399-
400-
// There's no segment, use the domain name.
401-
string domain;
402-
switch (parts.Length)
396+
var domain = parts.Length switch
403397
{
404-
case 1:
405-
case 2:
406-
// It's localhost if 1, no www if 2
407-
domain = parts.First();
408-
break;
409-
case 3:
410-
domain = parts[1];
411-
break;
412-
default:
413-
throw new NotImplementedException("We don't handle the case that the Host has more than three segments");
414-
}
398+
1 or 2 => parts.First(), // It's localhost or somewhere in an Intranet if 1; no www if 2.
399+
3 => parts[1], // Grab XYZ in www.XYZ.domain.com or similar.
400+
_ => throw new NotImplementedException("We don't handle the case that the Host has more than three segments"),
401+
};
415402

416403
result = domain + DefaultExtension;
417404
}
@@ -420,7 +407,7 @@ private string GetFileNameFromResponse(IHttpResponseMessageWrapper response, str
420407
return result;
421408
}
422409

423-
internal CodeGenerator? GetCodeGenerator(CommandOption codeGeneratorOption)
410+
internal static CodeGenerator? GetCodeGenerator(CommandOption codeGeneratorOption)
424411
{
425412
CodeGenerator? codeGenerator;
426413
if (codeGeneratorOption.HasValue())
@@ -435,7 +422,7 @@ private string GetFileNameFromResponse(IHttpResponseMessageWrapper response, str
435422
return codeGenerator;
436423
}
437424

438-
internal void ValidateCodeGenerator(CommandOption codeGeneratorOption)
425+
internal static void ValidateCodeGenerator(CommandOption codeGeneratorOption)
439426
{
440427
if (codeGeneratorOption.HasValue())
441428
{
@@ -494,7 +481,7 @@ private async Task<IDictionary<string, string>> LoadPackageVersionsFromURLAsync(
494481

495482
private static IDictionary<string, string> GetServicePackages(CodeGenerator? type)
496483
{
497-
CodeGenerator generator = type ?? CodeGenerator.NSwagCSharp;
484+
var generator = type ?? CodeGenerator.NSwagCSharp;
498485
var name = Enum.GetName(typeof(CodeGenerator), generator);
499486
var attributes = typeof(Program).Assembly.GetCustomAttributes<OpenApiDependencyAttribute>();
500487

@@ -513,10 +500,8 @@ private static IDictionary<string, string> GetServicePackages(CodeGenerator? typ
513500

514501
private static byte[] GetHash(Stream stream)
515502
{
516-
using (var algorithm = SHA256.Create())
517-
{
518-
return algorithm.ComputeHash(stream);
519-
}
503+
using var algorithm = SHA256.Create();
504+
return algorithm.ComputeHash(stream);
520505
}
521506

522507
private async Task WriteToFileAsync(Stream content, string destinationPath, bool overwrite)
@@ -572,12 +557,13 @@ private async Task WriteToFileAsync(Stream content, string destinationPath, bool
572557

573558
// Create or overwrite the destination file.
574559
reachedCopy = true;
575-
using var fileStream = new FileStream(destinationPath, FileMode.OpenOrCreate, FileAccess.Write);
560+
using var fileStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write);
576561
fileStream.Seek(0, SeekOrigin.Begin);
577562
if (content.CanSeek)
578563
{
579564
content.Seek(0, SeekOrigin.Begin);
580565
}
566+
581567
await content.CopyToAsync(fileStream);
582568
}
583569
catch (Exception ex)

src/Tools/Microsoft.dotnet-openapi/test/OpenApiAddFileTests.cs

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.IO;
54
using System.Text.RegularExpressions;
6-
using System.Threading.Tasks;
75
using System.Xml;
8-
using Microsoft.AspNetCore.Testing;
96
using Microsoft.DotNet.OpenApi.Tests;
10-
using Xunit;
117
using Xunit.Abstractions;
128

139
namespace Microsoft.DotNet.OpenApi.Add.Tests
@@ -16,18 +12,18 @@ public class OpenApiAddFileTests : OpenApiTestBase
1612
{
1713
public OpenApiAddFileTests(ITestOutputHelper output) : base(output) { }
1814

19-
[Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
15+
[Fact]
2016
public void OpenApi_Empty_ShowsHelp()
2117
{
2218
var app = GetApplication();
23-
var run = app.Execute(new string[] { });
19+
var run = app.Execute(Array.Empty<string>());
2420

2521
AssertNoErrors(run);
2622

2723
Assert.Contains("Usage: openapi ", _output.ToString());
2824
}
2925

30-
[Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
26+
[Fact]
3127
public void OpenApi_NoProjectExists()
3228
{
3329
var app = GetApplication();
@@ -38,7 +34,7 @@ public void OpenApi_NoProjectExists()
3834
Assert.Equal(1, run);
3935
}
4036

41-
[Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
37+
[Fact]
4238
public void OpenApi_ExplicitProject_Missing()
4339
{
4440
var app = GetApplication();
@@ -50,7 +46,7 @@ public void OpenApi_ExplicitProject_Missing()
5046
Assert.Equal(1, run);
5147
}
5248

53-
[Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
49+
[Fact]
5450
public void OpenApi_Add_Empty_ShowsHelp()
5551
{
5652
var app = GetApplication();
@@ -61,7 +57,7 @@ public void OpenApi_Add_Empty_ShowsHelp()
6157
Assert.Contains("Usage: openapi add", _output.ToString());
6258
}
6359

64-
[Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
60+
[Fact]
6561
public void OpenApi_Add_File_Empty_ShowsHelp()
6662
{
6763
var app = GetApplication();
@@ -72,7 +68,7 @@ public void OpenApi_Add_File_Empty_ShowsHelp()
7268
Assert.Contains("Usage: openapi ", _output.ToString());
7369
}
7470

75-
[Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
71+
[Fact]
7672
public async Task OpenApi_Add_ReuseItemGroup()
7773
{
7874
var project = CreateBasicProject(withOpenApi: true);
@@ -101,7 +97,7 @@ public async Task OpenApi_Add_ReuseItemGroup()
10197
Assert.Same(openApiRefs[0].ParentNode, openApiRefs[1].ParentNode);
10298
}
10399

104-
[Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
100+
[Fact]
105101
public void OpenApi_Add_File_EquivilentPaths()
106102
{
107103
var project = CreateBasicProject(withOpenApi: true);
@@ -126,7 +122,7 @@ public void OpenApi_Add_File_EquivilentPaths()
126122
Assert.Single(openApiRefs);
127123
}
128124

129-
[Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
125+
[Fact]
130126
public async Task OpenApi_Add_NSwagTypeScript()
131127
{
132128
var project = CreateBasicProject(withOpenApi: true);
@@ -146,7 +142,7 @@ public async Task OpenApi_Add_NSwagTypeScript()
146142
Assert.Contains($"<OpenApiReference Include=\"{nswagJsonFile}\" CodeGenerator=\"NSwagTypeScript\" />", content);
147143
}
148144

149-
[Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
145+
[Fact]
150146
public async Task OpenApi_Add_FromJson()
151147
{
152148
var project = CreateBasicProject(withOpenApi: true);
@@ -166,7 +162,7 @@ public async Task OpenApi_Add_FromJson()
166162
Assert.Contains($"<OpenApiReference Include=\"{nswagJsonFile}\"", content);
167163
}
168164

169-
[Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
165+
[Fact]
170166
public async Task OpenApi_Add_File_UseProjectOption()
171167
{
172168
var project = CreateBasicProject(withOpenApi: true);
@@ -186,7 +182,7 @@ public async Task OpenApi_Add_File_UseProjectOption()
186182
Assert.Contains($"<OpenApiReference Include=\"{nswagJsonFIle}\"", content);
187183
}
188184

189-
[Fact(Skip = "https://github.com/dotnet/aspnetcore/issues/32686")]
185+
[Fact]
190186
public async Task OpenApi_Add_MultipleTimes_OnlyOneReference()
191187
{
192188
var project = CreateBasicProject(withOpenApi: true);

0 commit comments

Comments
 (0)