Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
"program": "${workspaceFolder}/src/TodoApp/bin/Debug/net9.0/TodoApp.dll",
"program": "${workspaceFolder}/src/TodoApp/bin/Debug/net10.0/TodoApp.dll",
"args": [],
"cwd": "${workspaceFolder}/src/TodoApp",
"stopAtEntry": false,
Expand Down
2 changes: 1 addition & 1 deletion .vsconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"version": "1.0",
"components": [
"Component.Microsoft.VisualStudio.RazorExtension",
"Microsoft.NetCore.Component.Runtime.9.0",
"Microsoft.NetCore.Component.Runtime.10.0",
"Microsoft.NetCore.Component.SDK",
"Microsoft.VisualStudio.Component.CoreEditor",
"Microsoft.VisualStudio.Component.JavaScript.Diagnostics",
Expand Down
4 changes: 4 additions & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
<configuration>
<packageSources>
<clear />
<add key="domaindrivendev" value="https://www.myget.org/F/domaindrivendev/api/v3/index.json" />
<add key="NuGet" value="https://api.nuget.org/v3/index.json" />
</packageSources>
<packageSourceMapping>
<packageSource key="domaindrivendev">
<package pattern="Swashbuckle.AspNetCore*" />
</packageSource>
<packageSource key="NuGet">
<package pattern="*" />
</packageSource>
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "9.0.300",
"version": "10.0.100-preview.5.25277.114",
"allowPrerelease": false,
"rollForward": "latestMajor"
}
Expand Down
2 changes: 1 addition & 1 deletion perf/TodoApp.Benchmarks/TodoApp.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<RootNamespace>TodoApp</RootNamespace>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\TodoApp\TodoApp.csproj" />
Expand Down
21 changes: 12 additions & 9 deletions src/TodoApp/OpenApi/AspNetCore/AspNetCoreOpenApiEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;
using Microsoft.OpenApi.Models.References;

namespace TodoApp.OpenApi.AspNetCore;

Expand All @@ -14,6 +16,9 @@ public static IServiceCollection AddAspNetCoreOpenApi(this IServiceCollection se
// Add a document transformer to customise the generated OpenAPI document
options.AddDocumentTransformer((document, _, _) =>
{
// TODO Use 3.1 when all three OpenAPI implementations support it
options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_0;

// Add a title and version for the OpenAPI document
document.Info.Title = "Todo API (ASP.NET Core OpenAPI)";
document.Info.Description = "An API for managing Todo items.";
Expand All @@ -39,18 +44,16 @@ public static IServiceCollection AddAspNetCoreOpenApi(this IServiceCollection se
Description = "Bearer authentication using a JWT.",
Scheme = "bearer",
Type = SecuritySchemeType.Http,
Reference = new()
{
Id = "Bearer",
Type = ReferenceType.SecurityScheme,
},
};

var referenceId = "Bearer";
var reference = new OpenApiSecuritySchemeReference(referenceId, document);

document.Components ??= new();
document.Components.SecuritySchemes ??= new Dictionary<string, OpenApiSecurityScheme>();
document.Components.SecuritySchemes[scheme.Reference.Id] = scheme;
document.SecurityRequirements ??= [];
document.SecurityRequirements.Add(new() { [scheme] = [] });
document.Components.SecuritySchemes ??= [];
document.Components.SecuritySchemes[referenceId] = scheme;
document.Security ??= [];
document.Security.Add(new() { [reference] = [] });

return Task.CompletedTask;
});
Expand Down
87 changes: 6 additions & 81 deletions src/TodoApp/OpenApi/ExampleFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Microsoft.OpenApi.Any;

namespace TodoApp.OpenApi;

Expand All @@ -19,9 +19,9 @@ internal static class ExampleFormatter
/// <typeparam name="TProvider">The type of the example provider.</typeparam>
/// <param name="context">The JSON serializer context to use.</param>
/// <returns>
/// The <see cref="IOpenApiAny"/> to use as the example.
/// The <see cref="JsonNode"/> to use as the example.
/// </returns>
public static IOpenApiAny AsJson<TSchema, TProvider>(JsonSerializerContext context)
public static JsonNode? AsJson<TSchema, TProvider>(JsonSerializerContext context)
where TProvider : IExampleProvider<TSchema>
=> AsJson(TProvider.GenerateExample(), context);

Expand All @@ -32,87 +32,12 @@ public static IOpenApiAny AsJson<TSchema, TProvider>(JsonSerializerContext conte
/// <param name="example">The example value to format as JSON.</param>
/// <param name="context">The JSON serializer context to use.</param>
/// <returns>
/// The <see cref="IOpenApiAny"/> to use as the example.
/// The <see cref="JsonNode"/> to use as the example.
/// </returns>
public static IOpenApiAny AsJson<T>(T example, JsonSerializerContext context)
public static JsonNode? AsJson<T>(T example, JsonSerializerContext context)
{
// Apply any formatting rules configured for the API (e.g. camel casing)
var json = JsonSerializer.Serialize(example, typeof(T), context);
using var document = JsonDocument.Parse(json);

if (document.RootElement.ValueKind == JsonValueKind.String)
{
return new OpenApiString(document.RootElement.ToString());
}

var result = new OpenApiObject();

// Recursively build up the example from the properties of the object
foreach (var token in document.RootElement.EnumerateObject())
{
if (TryParse(token.Value, out var any))
{
result[token.Name] = any;
}
}

return result;
}

private static bool TryParse(JsonElement token, out IOpenApiAny? any)
{
any = null;

switch (token.ValueKind)
{
case JsonValueKind.Array:
var array = new OpenApiArray();

foreach (var value in token.EnumerateArray())
{
if (TryParse(value, out var child))
{
array.Add(child);
}
}

any = array;
return true;

case JsonValueKind.False:
any = new OpenApiBoolean(false);
return true;

case JsonValueKind.True:
any = new OpenApiBoolean(true);
return true;

case JsonValueKind.Number:
any = new OpenApiDouble(token.GetDouble());
return true;

case JsonValueKind.String:
any = new OpenApiString(token.GetString());
return true;

case JsonValueKind.Object:
var obj = new OpenApiObject();

foreach (var child in token.EnumerateObject())
{
if (TryParse(child.Value, out var value))
{
obj[child.Name] = value;
}
}

any = obj;
return true;

case JsonValueKind.Null:
case JsonValueKind.Undefined:
default:
return false;
}
return JsonNode.Parse(json);
}
}
12 changes: 8 additions & 4 deletions src/TodoApp/OpenApi/ExamplesProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;

namespace TodoApp.OpenApi;

Expand Down Expand Up @@ -56,7 +57,7 @@ protected void Process(OpenApiSchema schema, Type type)
}

private static void TryAddParameterExamples(
IList<OpenApiParameter> parameters,
IList<IOpenApiParameter> parameters,
ApiDescription description,
IList<IOpenApiExampleMetadata> examples)
{
Expand Down Expand Up @@ -89,11 +90,14 @@ private static void TryAddParameterExamples(
}

private static void TryAddRequestExamples(
OpenApiRequestBody body,
IOpenApiRequestBody body,
ApiDescription description,
IList<IOpenApiExampleMetadata> examples)
{
if (!body.Content.TryGetValue("application/json", out var mediaType) || mediaType.Example is not null)
if (body is null ||
body.Content is null ||
!body.Content.TryGetValue("application/json", out var mediaType) ||
mediaType.Example is not null)
{
return;
}
Expand Down Expand Up @@ -130,7 +134,7 @@ private static void TryAddResponseExamples(
foreach (var responseFormat in schemaResponse.ApiResponseFormats)
{
if (responses.TryGetValue(schemaResponse.StatusCode.ToString(CultureInfo.InvariantCulture), out var response) &&
response.Content.TryGetValue(responseFormat.MediaType, out var mediaType))
response.Content?.TryGetValue(responseFormat.MediaType, out var mediaType) is true)
{
mediaType.Example ??= (metadata ?? examples.SingleOrDefault((p) => p.SchemaType == schemaResponse.Type))?.GenerateExample(Context);
}
Expand Down
6 changes: 3 additions & 3 deletions src/TodoApp/OpenApi/IOpenApiExampleMetadata.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Copyright (c) Martin Costello, 2024. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Microsoft.OpenApi.Any;

namespace TodoApp.OpenApi;

Expand All @@ -29,7 +29,7 @@ public interface IOpenApiExampleMetadata
/// </summary>
/// <param name="context">The JSON serializer context to use to generate the example.</param>
/// <returns>
/// The OpenAPI example to use.
/// The OpenAPI example to use, if any.
/// </returns>
IOpenApiAny GenerateExample(JsonSerializerContext context);
JsonNode? GenerateExample(JsonSerializerContext context);
}
4 changes: 2 additions & 2 deletions src/TodoApp/OpenApi/OpenApiExampleAttribute`2.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Copyright (c) Martin Costello, 2024. All rights reserved.
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Microsoft.OpenApi.Any;

namespace TodoApp.OpenApi;

Expand Down Expand Up @@ -30,6 +30,6 @@ public class OpenApiExampleAttribute<TSchema, TProvider> : Attribute, IOpenApiEx
object? IOpenApiExampleMetadata.GenerateExample() => GenerateExample();

/// <inheritdoc/>
IOpenApiAny IOpenApiExampleMetadata.GenerateExample(JsonSerializerContext context)
JsonNode? IOpenApiExampleMetadata.GenerateExample(JsonSerializerContext context)
=> ExampleFormatter.AsJson(GenerateExample(), context);
}
10 changes: 8 additions & 2 deletions src/TodoApp/OpenApi/Swashbuckle/ExampleFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.Interfaces;
using Swashbuckle.AspNetCore.SwaggerGen;

namespace TodoApp.OpenApi.Swashbuckle;
Expand All @@ -16,6 +17,11 @@ public void Apply(OpenApiOperation operation, OperationFilterContext context)
=> Process(operation, context.ApiDescription);

/// <inheritdoc />
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
=> Process(schema, context.Type);
public void Apply(IOpenApiSchema schema, SchemaFilterContext context)
{
if (schema is OpenApiSchema concrete)
{
Process(concrete, context.Type);
}
}
}
12 changes: 4 additions & 8 deletions src/TodoApp/OpenApi/Swashbuckle/SwashbuckleOpenApiEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the Apache 2.0 license. See the LICENSE file in the project root for full license information.

using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Models.References;

namespace TodoApp.OpenApi.Swashbuckle;

Expand Down Expand Up @@ -39,15 +40,10 @@ public static IServiceCollection AddSwashbuckleOpenApi(this IServiceCollection s
Description = "Bearer authentication using a JWT.",
Scheme = "bearer",
Type = SecuritySchemeType.Http,
Reference = new()
{
Id = "Bearer",
Type = ReferenceType.SecurityScheme,
},
UnresolvedReference = false,
};
options.AddSecurityDefinition(scheme.Reference.Id, scheme);
options.AddSecurityRequirement(new() { [scheme] = [] });

options.AddSecurityDefinition("Bearer", scheme);
options.AddSecurityRequirement((document) => new() { [new("Bearer", document)] = [] });

// Enable reading OpenAPI metadata from attributes
options.EnableAnnotations();
Expand Down
14 changes: 7 additions & 7 deletions src/TodoApp/TodoApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@
<OpenApiGenerateDocumentsOnBuild>true</OpenApiGenerateDocumentsOnBuild>
<OpenApiGenerateDocumentsOptions>--openapi-version OpenApi3_0</OpenApiGenerateDocumentsOptions>
<RootNamespace>TodoApp</RootNamespace>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
<TypeScriptToolsVersion>latest</TypeScriptToolsVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.ApiDescription.Server" Version="9.0.5" PrivateAssets="all" />
<PackageReference Include="Microsoft.OpenApi" Version="1.6.24" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0-preview.4.25258.110" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.0-preview.4.25258.110" />
<PackageReference Include="Microsoft.Extensions.ApiDescription.Server" Version="10.0.0-preview.4.25258.110" PrivateAssets="all" />
<PackageReference Include="Microsoft.OpenApi" Version="2.0.0-preview.17" />
<PackageReference Include="Microsoft.TypeScript.MSBuild" Version="5.8.3" PrivateAssets="all" />
<PackageReference Include="NSwag.AspNetCore" Version="14.4.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="8.1.4" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.0-pr.3283.1497" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="9.0.0-pr.3283.1497" />
</ItemGroup>
<ItemGroup>
<Content Update="package.json;package-lock.json;tsconfig.json" CopyToPublishDirectory="Never" />
Expand Down
2 changes: 1 addition & 1 deletion startvscode.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ SET DOTNET_ROOT(x86)=%~dp0.dotnet\x86
SET PATH=%DOTNET_ROOT%;%PATH%

:: Sets the Target Framework for Visual Studio Code.
SET TARGET=net9.0
SET TARGET=net10.0

SET FOLDER=%~1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -299,8 +299,8 @@
},
status: {
type: integer,
format: int32,
nullable: true
nullable: true,
format: int32
},
detail: {
type: string,
Expand Down
Loading