-
-
Notifications
You must be signed in to change notification settings - Fork 50
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 #141 from christianhelle/openapi-validation
- Loading branch information
Showing
9 changed files
with
262 additions
and
5 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
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
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
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
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
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,82 @@ | ||
using Microsoft.OpenApi.Models; | ||
using Microsoft.OpenApi.Services; | ||
|
||
namespace Refitter.Validation; | ||
|
||
public class OpenApiStats : OpenApiVisitorBase | ||
{ | ||
public int ParameterCount { get; set; } = 0; | ||
public int SchemaCount { get; set; } = 0; | ||
public int HeaderCount { get; set; } = 0; | ||
public int PathItemCount { get; set; } = 0; | ||
public int RequestBodyCount { get; set; } = 0; | ||
public int ResponseCount { get; set; } = 0; | ||
public int OperationCount { get; set; } = 0; | ||
public int LinkCount { get; set; } = 0; | ||
public int CallbackCount { get; set; } = 0; | ||
|
||
public override void Visit(OpenApiParameter parameter) | ||
{ | ||
ParameterCount++; | ||
} | ||
|
||
public override void Visit(OpenApiSchema schema) | ||
{ | ||
SchemaCount++; | ||
} | ||
|
||
|
||
public override void Visit(IDictionary<string, OpenApiHeader> headers) | ||
{ | ||
HeaderCount++; | ||
} | ||
|
||
|
||
public override void Visit(OpenApiPathItem pathItem) | ||
{ | ||
PathItemCount++; | ||
} | ||
|
||
|
||
public override void Visit(OpenApiRequestBody requestBody) | ||
{ | ||
RequestBodyCount++; | ||
} | ||
|
||
|
||
public override void Visit(OpenApiResponses response) | ||
{ | ||
ResponseCount++; | ||
} | ||
|
||
|
||
public override void Visit(OpenApiOperation operation) | ||
{ | ||
OperationCount++; | ||
} | ||
|
||
|
||
public override void Visit(OpenApiLink link) | ||
{ | ||
LinkCount++; | ||
} | ||
|
||
public override void Visit(OpenApiCallback callback) | ||
{ | ||
CallbackCount++; | ||
} | ||
|
||
public override string ToString() | ||
{ | ||
return $""" | ||
- Path Items: {PathItemCount} | ||
- Operations: {OperationCount} | ||
- Parameters: {ParameterCount} | ||
- Request Bodies: {RequestBodyCount} | ||
- Responses: {ResponseCount} | ||
- Links: {LinkCount} | ||
- Callbacks: {CallbackCount} | ||
- Schemas: {SchemaCount} | ||
"""; | ||
} | ||
} |
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,22 @@ | ||
using System.Runtime.Serialization; | ||
|
||
namespace Refitter.Validation; | ||
|
||
[Serializable] | ||
public class OpenApiValidationException : Exception | ||
{ | ||
public OpenApiValidationResult ValidationResult { get; } = null!; | ||
|
||
public OpenApiValidationException( | ||
OpenApiValidationResult validationResult) | ||
: base("OpenAPI validation failed") | ||
{ | ||
ValidationResult = validationResult; | ||
} | ||
|
||
protected OpenApiValidationException( | ||
SerializationInfo info, | ||
StreamingContext context) : base(info, context) | ||
{ | ||
} | ||
} |
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,16 @@ | ||
using Microsoft.OpenApi.Readers; | ||
|
||
namespace Refitter.Validation; | ||
|
||
public record OpenApiValidationResult( | ||
OpenApiDiagnostic Diagnostics, | ||
OpenApiStats Statistics) | ||
{ | ||
public bool IsValid => Diagnostics.Errors.Count == 0; | ||
|
||
public void ThrowIfInvalid() | ||
{ | ||
if (!IsValid) | ||
throw new OpenApiValidationException(this); | ||
} | ||
} |
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,78 @@ | ||
using System.Net; | ||
using System.Security; | ||
|
||
using Microsoft.OpenApi.Readers; | ||
using Microsoft.OpenApi.Services; | ||
|
||
namespace Refitter.Validation; | ||
|
||
public static class OpenApiValidator | ||
{ | ||
public static async Task<OpenApiValidationResult> Validate(string openApiPath) | ||
{ | ||
var result = await ParseOpenApi(openApiPath); | ||
|
||
var statsVisitor = new OpenApiStats(); | ||
var walker = new OpenApiWalker(statsVisitor); | ||
walker.Walk(result.OpenApiDocument); | ||
|
||
return new( | ||
result.OpenApiDiagnostic, | ||
statsVisitor); | ||
} | ||
|
||
private static async Task<Stream> GetStream( | ||
string input, | ||
CancellationToken cancellationToken) | ||
{ | ||
if (input.StartsWith("http")) | ||
{ | ||
try | ||
{ | ||
var httpClientHandler = new HttpClientHandler() | ||
{ | ||
SslProtocols = System.Security.Authentication.SslProtocols.Tls12, | ||
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | ||
}; | ||
using var httpClient = new HttpClient(httpClientHandler); | ||
httpClient.DefaultRequestVersion = HttpVersion.Version20; | ||
return await httpClient.GetStreamAsync(input, cancellationToken); | ||
} | ||
catch (HttpRequestException ex) | ||
{ | ||
throw new InvalidOperationException($"Could not download the file at {input}", ex); | ||
} | ||
} | ||
|
||
try | ||
{ | ||
var fileInput = new FileInfo(input); | ||
return fileInput.OpenRead(); | ||
} | ||
catch (Exception ex) when (ex is FileNotFoundException || | ||
ex is PathTooLongException || | ||
ex is DirectoryNotFoundException || | ||
ex is IOException || | ||
ex is UnauthorizedAccessException || | ||
ex is SecurityException || | ||
ex is NotSupportedException) | ||
{ | ||
throw new InvalidOperationException($"Could not open the file at {input}", ex); | ||
} | ||
} | ||
|
||
private static async Task<ReadResult> ParseOpenApi(string openApiFile) | ||
{ | ||
var directoryName = new FileInfo(openApiFile).DirectoryName; | ||
var openApiReaderSettings = new OpenApiReaderSettings | ||
{ | ||
BaseUrl = openApiFile.StartsWith("http", StringComparison.OrdinalIgnoreCase) | ||
? new Uri(openApiFile) | ||
: new Uri($"file://{directoryName}{Path.DirectorySeparatorChar}") | ||
}; | ||
|
||
await using var stream = await GetStream(openApiFile, CancellationToken.None); | ||
var reader = new OpenApiStreamReader(openApiReaderSettings); | ||
return await reader.ReadAsync(stream, CancellationToken.None); | ||
} | ||
} |