Skip to content

Commit 4d7ec32

Browse files
authored
Initial Support for OpenAPI v3.0.1 description document creation (#1462)
## Why make this change? - Closes #916 by adding support for creating an OpenAPI description document for the Data API builder REST endpoint. https://spec.openapis.org/oas/v3.0.3 ## What is this change? - Uses Microsoft/OpenAPI.NET to generate an OpenAPI description document for entities defined in the runtime config which are used for the REST endpoint. ## How was this tested? - [x] Integration Tests - [ ] Unit Tests ## Sample Request(s) - https://localhost:5001/swagger -> Swagger UI visualization of generated document. - https://localhost:5001/openapi -> - [GET] Download/View Generated document
1 parent f3eac1f commit 4d7ec32

File tree

16 files changed

+1664
-125
lines changed

16 files changed

+1664
-125
lines changed

src/Config/DataApiBuilderException.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,19 @@ public enum SubStatusCodes
8585
/// <summary>
8686
/// Error encountered while doing data type conversions.
8787
/// </summary>
88-
ErrorProcessingData
88+
ErrorProcessingData,
89+
/// <summary>
90+
/// Attempting to generate OpenAPI document when one already exists.
91+
/// </summary>
92+
OpenApiDocumentAlreadyExists,
93+
/// <summary>
94+
/// Attempt to create OpenAPI document failed.
95+
/// </summary>
96+
OpenApiDocumentCreationFailure,
97+
/// <summary>
98+
/// Global REST endpoint disabled in runtime configuration.
99+
/// </summary>
100+
GlobalRestEndpointDisabled
89101
}
90102

91103
public HttpStatusCode StatusCode { get; }

src/Directory.Packages.props

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@
1717
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="6.0.14" />
1818
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="6.0.14" />
1919
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.20.0" />
20-
<!--When updating Microsoft.Data.SqlClient, update license URL in scripts/notice-generation.ps1-->
20+
<!--When updating Microsoft.Data.SqlClient, update license URL in scripts/notice-generation.ps1-->
2121
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.0.1" />
2222
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
2323
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" />
2424
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
2525
<PackageVersion Include="Microsoft.OData.Edm" Version="7.12.5" />
2626
<PackageVersion Include="Microsoft.OData.Core" Version="7.12.5" />
27+
<PackageVersion Include="Microsoft.OpenApi" Version="1.6.3" />
2728
<PackageVersion Include="Moq" Version="4.18.2" />
2829
<PackageVersion Include="MSTest.TestAdapter" Version="2.2.10" />
2930
<PackageVersion Include="MSTest.TestFramework" Version="2.2.10" />
@@ -32,6 +33,7 @@
3233
<PackageVersion Include="Npgsql" Version="7.0.1" />
3334
<PackageVersion Include="Polly" Version="7.2.3" />
3435
<PackageVersion Include="StyleCop.Analyzers" Version="1.1.118" />
36+
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.5.0" />
3537
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
3638
<PackageVersion Include="System.Drawing.Common" Version="6.0.0" />
3739
<PackageVersion Include="System.IO.Abstractions" Version="17.2.3" />

src/Service.Tests/Configuration/ConfigurationTests.cs

Lines changed: 319 additions & 19 deletions
Large diffs are not rendered by default.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
namespace Azure.DataApiBuilder.Service.Tests
5+
{
6+
public class OpenApiDocumentorConstants
7+
{
8+
public const string TOPLEVELPROPERTY_OPENAPI = "openapi";
9+
public const string TOPLEVELPROPERTY_INFO = "info";
10+
public const string TOPLEVELPROPERTY_SERVERS = "servers";
11+
public const string TOPLEVELPROPERTY_PATHS = "paths";
12+
public const string TOPLEVELPROPERTY_COMPONENTS = "components";
13+
public const string PROPERTY_SCHEMAS = "schemas";
14+
}
15+
}

src/Service.Tests/Unittests/RestServiceUnitTests.cs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ public class RestServiceUnitTests
2727
#region Positive Cases
2828

2929
/// <summary>
30-
/// Test the REST Service for parsing entity name
31-
/// and primary key route from the route, given a
32-
/// particular path.
30+
/// Validates that the RestService helper function GetEntityNameAndPrimaryKeyRouteFromRoute
31+
/// properly parses the entity name and primary key route from the route,
32+
/// given the input path (which does not include the path base).
3333
/// </summary>
3434
/// <param name="route">The route to parse.</param>
3535
/// <param name="path">The path that the route starts with.</param>
@@ -42,14 +42,16 @@ public class RestServiceUnitTests
4242
[DataRow("rest api/Book/id/1", "/rest api", "Book", "id/1")]
4343
[DataRow(" rest_api/commodities/categoryid/1/pieceid/1", "/ rest_api", "commodities", "categoryid/1/pieceid/1")]
4444
[DataRow("rest-api/Book/id/1", "/rest-api", "Book", "id/1")]
45-
public void ParseEntityNameAndPrimaryKeyTest(string route,
46-
string path,
47-
string expectedEntityName,
48-
string expectedPrimaryKeyRoute)
45+
public void ParseEntityNameAndPrimaryKeyTest(
46+
string route,
47+
string path,
48+
string expectedEntityName,
49+
string expectedPrimaryKeyRoute)
4950
{
5051
InitializeTest(path, expectedEntityName);
52+
string routeAfterPathBase = _restService.GetRouteAfterPathBase(route);
5153
(string actualEntityName, string actualPrimaryKeyRoute) =
52-
_restService.GetEntityNameAndPrimaryKeyRouteFromRoute(route);
54+
_restService.GetEntityNameAndPrimaryKeyRouteFromRoute(routeAfterPathBase);
5355
Assert.AreEqual(expectedEntityName, actualEntityName);
5456
Assert.AreEqual(expectedPrimaryKeyRoute, actualPrimaryKeyRoute);
5557
}
@@ -76,8 +78,7 @@ public void ErrorForInvalidRouteAndPathToParseTest(string route,
7678
InitializeTest(path, route);
7779
try
7880
{
79-
(string actualEntityName, string actualPrimaryKeyRoute) =
80-
_restService.GetEntityNameAndPrimaryKeyRouteFromRoute(route);
81+
string routeAfterPathBase = _restService.GetRouteAfterPathBase(route);
8182
}
8283
catch (DataApiBuilderException e)
8384
{

src/Service/Azure.DataApiBuilder.Service.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
5959
<PackageReference Include="Microsoft.OData.Edm" />
6060
<PackageReference Include="Microsoft.OData.Core" />
61+
<PackageReference Include="Microsoft.OpenApi" />
6162
<PackageReference Include="MSTest.TestFramework" />
6263
<PackageReference Include="MySqlConnector" />
6364
<PackageReference Include="Newtonsoft.Json" />
@@ -67,6 +68,7 @@
6768
<PrivateAssets>all</PrivateAssets>
6869
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
6970
</PackageReference>
71+
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" />
7072
<PackageReference Include="System.CommandLine" />
7173
<PackageReference Include="System.IO.Abstractions" />
7274
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All"/>

src/Service/Controllers/RestController.cs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33

44
using System;
55
using System.Net;
6+
using System.Net.Mime;
67
using System.Threading.Tasks;
78
using Azure.DataApiBuilder.Service.Exceptions;
89
using Azure.DataApiBuilder.Service.Models;
910
using Azure.DataApiBuilder.Service.Services;
11+
using Azure.DataApiBuilder.Service.Services.OpenAPI;
1012
using Microsoft.AspNetCore.Http;
1113
using Microsoft.AspNetCore.Http.Extensions;
1214
using Microsoft.AspNetCore.Mvc;
@@ -27,6 +29,11 @@ public class RestController : ControllerBase
2729
/// Service providing REST Api executions.
2830
/// </summary>
2931
private readonly RestService _restService;
32+
33+
/// <summary>
34+
/// OpenAPI description document creation service.
35+
/// </summary>
36+
private readonly IOpenApiDocumentor _openApiDocumentor;
3037
/// <summary>
3138
/// String representing the value associated with "code" for a server error
3239
/// </summary>
@@ -44,9 +51,10 @@ public class RestController : ControllerBase
4451
/// <summary>
4552
/// Constructor.
4653
/// </summary>
47-
public RestController(RestService restService, ILogger<RestController> logger)
54+
public RestController(RestService restService, IOpenApiDocumentor openApiDocumentor, ILogger<RestController> logger)
4855
{
4956
_restService = restService;
57+
_openApiDocumentor = openApiDocumentor;
5058
_logger = logger;
5159
}
5260

@@ -87,8 +95,8 @@ public async Task<IActionResult> Find(
8795
string route)
8896
{
8997
return await HandleOperation(
90-
route,
91-
Config.Operation.Read);
98+
route,
99+
Config.Operation.Read);
92100
}
93101

94102
/// <summary>
@@ -188,7 +196,21 @@ private async Task<IActionResult> HandleOperation(
188196
subStatusCode: DataApiBuilderException.SubStatusCodes.BadRequest);
189197
}
190198

191-
(string entityName, string primaryKeyRoute) = _restService.GetEntityNameAndPrimaryKeyRouteFromRoute(route);
199+
// Validate the PathBase matches the configured REST path.
200+
string routeAfterPathBase = _restService.GetRouteAfterPathBase(route);
201+
202+
// Explicitly handle OpenAPI description document retrieval requests.
203+
if (string.Equals(routeAfterPathBase, OpenApiDocumentor.OPENAPI_ROUTE, StringComparison.OrdinalIgnoreCase))
204+
{
205+
if (_openApiDocumentor.TryGetDocument(out string? document))
206+
{
207+
return Content(document, MediaTypeNames.Application.Json);
208+
}
209+
210+
return NotFound();
211+
}
212+
213+
(string entityName, string primaryKeyRoute) = _restService.GetEntityNameAndPrimaryKeyRouteFromRoute(routeAfterPathBase);
192214

193215
IActionResult? result = await _restService.ExecuteAsync(entityName, operationType, primaryKeyRoute);
194216

src/Service/Services/DbTypeHelper.cs

Lines changed: 0 additions & 65 deletions
This file was deleted.

src/Service/Services/MetadataProviders/SqlMetadataProvider.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ private async Task FillSchemaForStoredProcedureAsync(
317317
new()
318318
{
319319
SystemType = systemType,
320-
DbType = DbTypeHelper.GetDbTypeFromSystemType(systemType)
320+
DbType = TypeHelper.GetDbTypeFromSystemType(systemType)
321321
}
322322
);
323323
}
@@ -1016,7 +1016,7 @@ private async Task PopulateSourceDefinitionAsync(
10161016
IsNullable = (bool)columnInfoFromAdapter["AllowDBNull"],
10171017
IsAutoGenerated = (bool)columnInfoFromAdapter["IsAutoIncrement"],
10181018
SystemType = systemTypeOfColumn,
1019-
DbType = DbTypeHelper.GetDbTypeFromSystemType(systemTypeOfColumn)
1019+
DbType = TypeHelper.GetDbTypeFromSystemType(systemTypeOfColumn)
10201020
};
10211021

10221022
// Tests may try to add the same column simultaneously
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Diagnostics.CodeAnalysis;
5+
6+
namespace Azure.DataApiBuilder.Service.Services.OpenAPI
7+
{
8+
/// <summary>
9+
/// Interface for the service which generates and provides the OpenAPI description document
10+
/// describing the DAB engine's entity REST endpoint paths.
11+
/// </summary>
12+
public interface IOpenApiDocumentor
13+
{
14+
/// <summary>
15+
/// Attempts to return the OpenAPI description document, if generated.
16+
/// </summary>
17+
/// <param name="document">String representation of JSON OpenAPI description document.</param>
18+
/// <returns>True (plus string representation of document), when document exists. False, otherwise.</returns>
19+
public bool TryGetDocument([NotNullWhen(true)] out string? document);
20+
21+
/// <summary>
22+
/// Creates an OpenAPI description document using OpenAPI.NET.
23+
/// Document compliant with patches of OpenAPI V3.0 spec 3.0.0 and 3.0.1,
24+
/// aligned with specification support provided by Microsoft.OpenApi.
25+
/// </summary>
26+
/// <exception cref="DataApiBuilderException">Raised when document is already generated
27+
/// or a failure occurs during generation.</exception>
28+
/// <seealso cref="https://github.com/microsoft/OpenAPI.NET/blob/1.6.3/src/Microsoft.OpenApi/OpenApiSpecVersion.cs"/>
29+
public void CreateDocument();
30+
}
31+
}

0 commit comments

Comments
 (0)