-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #18 from Ellerbach/add-openapi-tool
Added OpenAPI tool
- Loading branch information
Showing
8 changed files
with
386 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
// Licensed to DocFX Companion Tools and contributors under one or more agreements. | ||
// DocFX Companion Tools and contributors licenses this file to you under the MIT license. | ||
|
||
namespace DocFxOpenApi | ||
{ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using CommandLine; | ||
using global::DocFxOpenApi.Domain; | ||
using global::DocFxOpenApi.Helpers; | ||
using Microsoft.OpenApi; | ||
using Microsoft.OpenApi.Extensions; | ||
using Microsoft.OpenApi.Models; | ||
using Microsoft.OpenApi.Readers; | ||
|
||
/// <summary> | ||
/// Open API file converter to V2 JSON files. | ||
/// </summary> | ||
internal class DocFxOpenApi | ||
{ | ||
private const OpenApiSpecVersion OutputVersion = OpenApiSpecVersion.OpenApi2_0; | ||
private static readonly string[] _openApiFileExtensions = { "json", "yaml", "yml" }; | ||
|
||
private static int _returnvalue; | ||
private CommandlineOptions _options; | ||
private MessageHelper _message; | ||
|
||
private DocFxOpenApi(CommandlineOptions thisOptions) | ||
{ | ||
_options = thisOptions; | ||
_message = new MessageHelper(thisOptions); | ||
} | ||
|
||
/// <summary> | ||
/// Main entry point. | ||
/// </summary> | ||
/// <param name="args">Commandline options described in <see cref="CommandlineOptions"/> class.</param> | ||
/// <returns>0 if successful, 1 on error.</returns> | ||
private static int Main(string[] args) | ||
{ | ||
Parser.Default.ParseArguments<CommandlineOptions>(args) | ||
.WithParsed(RunLogic) | ||
.WithNotParsed(HandleErrors); | ||
|
||
Console.WriteLine($"Exit with return code {_returnvalue}"); | ||
|
||
return _returnvalue; | ||
} | ||
|
||
/// <summary> | ||
/// Run the logic of the app with the given parameters. | ||
/// Given folders are checked if they exist. | ||
/// </summary> | ||
/// <param name="o">Parsed commandline options.</param> | ||
private static void RunLogic(CommandlineOptions o) | ||
{ | ||
new DocFxOpenApi(o).RunLogic(); | ||
} | ||
|
||
/// <summary> | ||
/// On parameter errors, we set the returnvalue to 1 to indicated an error. | ||
/// </summary> | ||
/// <param name="errors">List or errors (ignored).</param> | ||
private static void HandleErrors(IEnumerable<Error> errors) | ||
{ | ||
_returnvalue = 1; | ||
} | ||
|
||
private void RunLogic() | ||
{ | ||
if (string.IsNullOrEmpty(_options.OutputFolder)) | ||
{ | ||
_options.OutputFolder = _options.SpecFolder; | ||
} | ||
|
||
_message.Verbose($"Specification folder: {_options.SpecFolder}"); | ||
_message.Verbose($"Output folder : {_options.OutputFolder}"); | ||
_message.Verbose($"Verbose : {_options.Verbose}"); | ||
|
||
if (!Directory.Exists(_options.SpecFolder)) | ||
{ | ||
_message.Error($"ERROR: Specification folder '{_options.SpecFolder}' doesn't exist."); | ||
_returnvalue = 1; | ||
return; | ||
} | ||
|
||
Directory.CreateDirectory(_options.OutputFolder!); | ||
|
||
this.ConvertOpenApiFiles(); | ||
} | ||
|
||
private void ConvertOpenApiFiles() | ||
{ | ||
foreach (var extension in _openApiFileExtensions) | ||
{ | ||
this.ConvertOpenApiFiles(extension); | ||
} | ||
} | ||
|
||
private void ConvertOpenApiFiles(string extension) | ||
{ | ||
foreach (var file in Directory.GetFiles( | ||
_options.SpecFolder!, | ||
$"*.{extension}", | ||
new EnumerationOptions | ||
{ | ||
MatchCasing = MatchCasing.CaseInsensitive, | ||
RecurseSubdirectories = true, | ||
})) | ||
{ | ||
this.ConvertOpenApiFile(file); | ||
} | ||
} | ||
|
||
private void ConvertOpenApiFile(string inputSpecFile) | ||
{ | ||
_message.Verbose($"Reading OpenAPI file '{inputSpecFile}'"); | ||
using var stream = File.OpenRead(inputSpecFile); | ||
var document = new OpenApiStreamReader().Read(stream, out var diagnostic); | ||
|
||
if (diagnostic.Errors.Any()) | ||
{ | ||
_message.Error($"ERROR: Not a valid OpenAPI v2 or v3 specification"); | ||
foreach (var error in diagnostic.Errors) | ||
{ | ||
_message.Error(error.ToString()); | ||
} | ||
|
||
_returnvalue = 1; | ||
return; | ||
} | ||
|
||
_message.Verbose($"Input OpenAPI version '{diagnostic.SpecificationVersion}'"); | ||
|
||
foreach (var (pathName, path) in document.Paths) | ||
{ | ||
foreach (var (operationType, operation) in path.Operations) | ||
{ | ||
var description = $"{pathName} {operationType}"; | ||
|
||
foreach (var (responseType, response) in operation.Responses) | ||
{ | ||
foreach (var (mediaType, content) in response.Content) | ||
{ | ||
this.CreateSingleExampleFromMultipleExamples(content, $"{description} response {responseType} {mediaType}"); | ||
} | ||
} | ||
|
||
foreach (var parameter in operation.Parameters) | ||
{ | ||
foreach (var (mediaType, content) in parameter.Content) | ||
{ | ||
this.CreateSingleExampleFromMultipleExamples(content, $"{description} parameter {parameter.Name} {mediaType}"); | ||
} | ||
} | ||
|
||
if (operation.RequestBody is not null) | ||
{ | ||
foreach (var (mediaType, content) in operation.RequestBody.Content) | ||
{ | ||
this.CreateSingleExampleFromMultipleExamples(content, $"{description} requestBody {mediaType}"); | ||
|
||
if (content.Example is not null && content.Schema is not null && content.Schema.Example is null) | ||
{ | ||
_message.Verbose($"[OpenAPIv2 compatibility] Setting type example from sample requestBody example for {content.Schema.Reference?.ReferenceV2 ?? "item"} from {operation.OperationId}"); | ||
content.Schema.Example = content.Example; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
var outputFileName = Path.ChangeExtension(Path.GetFileName(inputSpecFile), ".swagger.json"); | ||
var outputFile = Path.Combine(_options.OutputFolder!, outputFileName); | ||
_message.Verbose($"Writing output file '{outputFile}' as version '{OutputVersion}'"); | ||
using FileStream fs = File.Create(outputFile); | ||
document.Serialize(fs, OutputVersion, OpenApiFormat.Json); | ||
} | ||
|
||
private void CreateSingleExampleFromMultipleExamples(OpenApiMediaType content, string description) | ||
{ | ||
if (content.Example is null && content.Examples.Any()) | ||
{ | ||
_message.Verbose($"[OpenAPIv2 compatibility] Setting example from first of multiple OpenAPIv3 examples for {description}"); | ||
content.Example = content.Examples.Values.First().Value; | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net6.0</TargetFramework> | ||
<Nullable>enable</Nullable> | ||
<GenerateDocumentationFile>true</GenerateDocumentationFile> | ||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="CommandLineParser" Version="2.8.0" /> | ||
<PackageReference Include="GitVersionTask" Version="5.5.1"> | ||
<PrivateAssets>all</PrivateAssets> | ||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||
</PackageReference> | ||
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.5.0" /> | ||
</ItemGroup> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio 15 | ||
VisualStudioVersion = 15.0.26124.0 | ||
MinimumVisualStudioVersion = 15.0.26124.0 | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DocFxOpenApi", "DocFxOpenApi.csproj", "{AFE399B5-4E43-4D11-A854-5E49EEFC9F2B}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|Any CPU = Debug|Any CPU | ||
Debug|x64 = Debug|x64 | ||
Debug|x86 = Debug|x86 | ||
Release|Any CPU = Release|Any CPU | ||
Release|x64 = Release|x64 | ||
Release|x86 = Release|x86 | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{AFE399B5-4E43-4D11-A854-5E49EEFC9F2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
{AFE399B5-4E43-4D11-A854-5E49EEFC9F2B}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
{AFE399B5-4E43-4D11-A854-5E49EEFC9F2B}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
{AFE399B5-4E43-4D11-A854-5E49EEFC9F2B}.Debug|x64.Build.0 = Debug|Any CPU | ||
{AFE399B5-4E43-4D11-A854-5E49EEFC9F2B}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
{AFE399B5-4E43-4D11-A854-5E49EEFC9F2B}.Debug|x86.Build.0 = Debug|Any CPU | ||
{AFE399B5-4E43-4D11-A854-5E49EEFC9F2B}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
{AFE399B5-4E43-4D11-A854-5E49EEFC9F2B}.Release|Any CPU.Build.0 = Release|Any CPU | ||
{AFE399B5-4E43-4D11-A854-5E49EEFC9F2B}.Release|x64.ActiveCfg = Release|Any CPU | ||
{AFE399B5-4E43-4D11-A854-5E49EEFC9F2B}.Release|x64.Build.0 = Release|Any CPU | ||
{AFE399B5-4E43-4D11-A854-5E49EEFC9F2B}.Release|x86.ActiveCfg = Release|Any CPU | ||
{AFE399B5-4E43-4D11-A854-5E49EEFC9F2B}.Release|x86.Build.0 = Release|Any CPU | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// Licensed to DocFX Companion Tools and contributors under one or more agreements. | ||
// DocFX Companion Tools and contributors licenses this file to you under the MIT license. | ||
|
||
namespace DocFxOpenApi.Domain | ||
{ | ||
using CommandLine; | ||
|
||
/// <summary> | ||
/// Class for command line options. | ||
/// </summary> | ||
public class CommandlineOptions | ||
{ | ||
/// <summary> | ||
/// Gets or sets the folder with specifications. | ||
/// </summary> | ||
[Option('s', "specfolder", Required = true, HelpText = "Folder containing the OpenAPI specification.")] | ||
public string? SpecFolder { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the output folder. | ||
/// </summary> | ||
[Option('o', "outputfolder", Required = false, HelpText = "Folder to write the resulting specifications in.")] | ||
public string? OutputFolder { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets a value indicating whether verbose information is shown in the output. | ||
/// </summary> | ||
[Option('v', "verbose", Required = false, HelpText = "Show verbose messages.")] | ||
public bool Verbose { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// This file is used by Code Analysis to maintain SuppressMessage | ||
// attributes that are applied to this project. | ||
// Project-level suppressions either have no target or are given | ||
// a specific target and scoped to a namespace, type, member, etc. | ||
|
||
using System.Diagnostics.CodeAnalysis; | ||
|
||
[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1633:File should have header", Justification = "We don't use file headers in DMP.")] | ||
[assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Literals are allowed in a tool. No need for localization.")] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// Licensed to DocFX Companion Tools and contributors under one or more agreements. | ||
// DocFX Companion Tools and contributors licenses this file to you under the MIT license. | ||
|
||
namespace DocFxOpenApi.Helpers | ||
{ | ||
using System; | ||
using global::DocFxOpenApi.Domain; | ||
|
||
/// <summary> | ||
/// Helper methods to write messages to the console. | ||
/// </summary> | ||
public class MessageHelper | ||
{ | ||
private readonly CommandlineOptions _options; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="MessageHelper"/> class. | ||
/// </summary> | ||
/// <param name="options">Command line options.</param> | ||
public MessageHelper(CommandlineOptions options) | ||
{ | ||
this._options = options; | ||
} | ||
|
||
/// <summary> | ||
/// Helper method for verbose messages. | ||
/// </summary> | ||
/// <param name="message">Message to show in verbose mode.</param> | ||
public void Verbose(string message) | ||
{ | ||
if (this._options.Verbose) | ||
{ | ||
Console.WriteLine(message); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Helper method for warning messages. | ||
/// </summary> | ||
/// <param name="message">Message to show in verbose mode.</param> | ||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "We want same access for all methods.")] | ||
public void Warning(string message) | ||
{ | ||
Console.ForegroundColor = ConsoleColor.Yellow; | ||
Console.WriteLine(message); | ||
Console.ResetColor(); | ||
} | ||
|
||
/// <summary> | ||
/// Helper method for error messages. | ||
/// </summary> | ||
/// <param name="message">Message to show in verbose mode.</param> | ||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "We want same access for all methods.")] | ||
public void Error(string message) | ||
{ | ||
Console.ForegroundColor = ConsoleColor.Red; | ||
Console.WriteLine(message); | ||
Console.ResetColor(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<configuration> | ||
<packageSources> | ||
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /> | ||
</packageSources> | ||
</configuration> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# OpenAPI specification converter for DocFX | ||
|
||
This tool converts existing [OpenAPI](https://www.openapis.org/) specification files into the format compatible with DocFX (OpenAPI v2 JSON files). It allows DocFX to generate HTML pages from the OpenAPI specification. OpenAPI is also known as [Swagger](https://swagger.io/). | ||
|
||
## Usage | ||
|
||
```text | ||
DocFxOpenApi -s <specs folder> [-o <output folder>] [-v] | ||
-s, --specfolder Required. Folder containing the OpenAPI specification. | ||
-o, --outputfolder Folder to write the resulting specifications in. | ||
-v, --verbose Show verbose messages. | ||
--help Display this help screen. | ||
--version Display version information. | ||
``` | ||
|
||
The tool converts any `*.json`, `*.yaml`, `*.yml` file from the provided specification folder into the output folder. It supports JSON or YAML-format, OpenAPI v2 or v3 (including 3.0.1) format files. | ||
|
||
If the `-o or --outputfolder` is not provided, the output folder is set to the input specs folder. | ||
|
||
|
||
If normal return code of the tool is 0, but on error it returns 1. | ||
|
||
## Warnings, errors and verbose | ||
|
||
If the tool encounters situations that might need some action, a warning is written to the output. The table of contents is still created. | ||
|
||
If the tool encounters an error, an error message is written to the output. The table of contents will not be created. The tool will return error code 1. | ||
|
||
If you want to trace what the tool is doing, use the `-v or verbose` flag to output all details of processing the files and folders and creating the table of contents. | ||
|
||
## Limitations and workarounds | ||
|
||
- DocFX only supports generating documentation [from OpenAPI v2 JSON files](https://dotnet.github.io/docfx/tutorial/intro_rest_api_documentation.html) as of May 2021. Therefore the utility converts input files into that format. | ||
- DocFX [does not include type definitions](https://github.com/dotnet/docfx/issues/2072) as of May 2021. | ||
- The OpenAPI v2 format does not allow providing multiple examples for result payloads. OpenAPI v3 allows providing either a single example or a collection of examples. If a collection of examples is provided, the utility uses the first example as an example in the output file. |