-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
- Loading branch information
1 parent
ba7d570
commit 4b0c692
Showing
19 changed files
with
2,100 additions
and
8 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
19 changes: 19 additions & 0 deletions
19
src/CommunityToolkit.Datasync.Server.NSwag/CommunityToolkit.Datasync.Server.NSwag.csproj
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> | ||
<Description>Provides necessary capabilities for supporting the Datasync server library when using NSwag for creating OpenAPI definitions.</Description> | ||
</PropertyGroup> | ||
|
||
<Import Project="..\Shared.Build.props" /> | ||
|
||
<ItemGroup> | ||
<InternalsVisibleTo Include="CommunityToolkit.Datasync.Server.NSwag.Test" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="NSwag.AspNetCore" Version="14.0.8" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\CommunityToolkit.Datasync.Server\CommunityToolkit.Datasync.Server.csproj" /> | ||
</ItemGroup> | ||
</Project> |
166 changes: 166 additions & 0 deletions
166
src/CommunityToolkit.Datasync.Server.NSwag/DatasyncOperationProcessor.cs
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,166 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using CommunityToolkit.Datasync.Server.Filters; | ||
using NJsonSchema; | ||
using NSwag; | ||
using NSwag.Generation.Processors; | ||
using NSwag.Generation.Processors.Contexts; | ||
using System.Net; | ||
using System.Reflection; | ||
|
||
namespace CommunityToolkit.Datasync.Server.NSwag; | ||
|
||
/// <summary> | ||
/// Implements an <see cref="IOperationProcessor"/> for handling datasync table controllers. | ||
/// </summary> | ||
public class DatasyncOperationProcessor : IOperationProcessor | ||
{ | ||
/// <summary>Processes the specified method information.</summary> | ||
/// <param name="context">The processor context.</param> | ||
/// <returns>true if the operation should be added to the Swagger specification.</returns> | ||
public bool Process(OperationProcessorContext context) | ||
{ | ||
if (IsTableController(context.ControllerType)) | ||
{ | ||
ProcessDatasyncOperation(context); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/// <summary> | ||
/// Determines if the controller type provided is a datasync table controller. | ||
/// </summary> | ||
/// <param name="type">The type of the table controller.</param> | ||
/// <returns><c>true</c> if the type is a datasync table controller.</returns> | ||
internal static bool IsTableController(Type type) | ||
{ | ||
if (!type.IsAbstract && type.BaseType != null && type.BaseType.IsGenericType == true) | ||
{ | ||
if (type.GetCustomAttribute<DatasyncControllerAttribute>() != null) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/// <summary> | ||
/// Returns the entity type being handled by the controller type. | ||
/// </summary> | ||
/// <param name="controllerType">The <see cref="Type"/> of the controller.</param> | ||
/// <returns>The Type for the entity.</returns> | ||
/// <exception cref="ArgumentException">If the controller type is not a generic type.</exception> | ||
internal static Type GetTableEntityType(Type controllerType) | ||
=> controllerType.BaseType?.GetGenericArguments().FirstOrDefault() | ||
?? throw new ArgumentException("Unable to retrieve generic entity type"); | ||
|
||
private static void ProcessDatasyncOperation(OperationProcessorContext context) | ||
{ | ||
OpenApiOperation operation = context.OperationDescription.Operation; | ||
string method = context.OperationDescription.Method; | ||
string path = context.OperationDescription.Path; | ||
Type entityType = GetTableEntityType(context.ControllerType); | ||
JsonSchema entitySchemaRef = GetEntityReference(context, entityType); | ||
|
||
if (method.Equals("DELETE", StringComparison.InvariantCultureIgnoreCase)) | ||
{ | ||
operation.AddConditionalRequestSupport(entitySchemaRef); | ||
operation.SetResponse(HttpStatusCode.NoContent); | ||
operation.SetResponse(HttpStatusCode.NotFound); | ||
operation.SetResponse(HttpStatusCode.Gone); | ||
} | ||
|
||
if (method.Equals("GET", StringComparison.InvariantCultureIgnoreCase) && path.EndsWith("/{id}")) | ||
{ | ||
operation.AddConditionalRequestSupport(entitySchemaRef, true); | ||
operation.SetResponse(HttpStatusCode.OK, entitySchemaRef); | ||
operation.SetResponse(HttpStatusCode.NotFound); | ||
} | ||
|
||
if (method.Equals("GET", StringComparison.InvariantCultureIgnoreCase) && !path.EndsWith("/{id}")) | ||
{ | ||
operation.AddODataQueryParameters(); | ||
operation.SetResponse(HttpStatusCode.OK, CreateListSchema(entitySchemaRef, entityType.Name), false); | ||
operation.SetResponse(HttpStatusCode.BadRequest); | ||
} | ||
|
||
if (method.Equals("POST", StringComparison.InvariantCultureIgnoreCase)) | ||
{ | ||
operation.AddConditionalRequestSupport(entitySchemaRef, true); | ||
operation.SetResponse(HttpStatusCode.Created, entitySchemaRef); | ||
operation.SetResponse(HttpStatusCode.BadRequest); | ||
} | ||
|
||
if (method.Equals("PUT", StringComparison.InvariantCultureIgnoreCase)) | ||
{ | ||
operation.AddConditionalRequestSupport(entitySchemaRef); | ||
operation.SetResponse(HttpStatusCode.OK, entitySchemaRef); | ||
operation.SetResponse(HttpStatusCode.BadRequest); | ||
operation.SetResponse(HttpStatusCode.NotFound); | ||
operation.SetResponse(HttpStatusCode.Gone); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Either reads or generates the required entity type schema. | ||
/// </summary> | ||
/// <param name="context">The context for the operation processor.</param> | ||
/// <param name="entityType">The entity type needed.</param> | ||
/// <returns>A reference to the entity schema.</returns> | ||
private static JsonSchema GetEntityReference(OperationProcessorContext context, Type entityType) | ||
{ | ||
string schemaName = context.SchemaGenerator.Settings.SchemaNameGenerator.Generate(entityType); | ||
if (!context.Document.Definitions.TryGetValue(schemaName, out JsonSchema? value)) | ||
{ | ||
JsonSchema newSchema = context.SchemaGenerator.Generate(entityType); | ||
value = newSchema; | ||
context.Document.Definitions.Add(schemaName, value); | ||
} | ||
|
||
JsonSchema actualSchema = value; | ||
return new JsonSchema { Reference = actualSchema }; | ||
} | ||
|
||
/// <summary> | ||
/// Creates the paged item schema reference. | ||
/// </summary> | ||
/// <param name="entitySchema">The entity schema reference.</param> | ||
/// <param name="entityName">The name of the entity handled by the list operation.</param> | ||
/// <returns>The list schema reference</returns> | ||
private static JsonSchema CreateListSchema(JsonSchema entitySchema, string entityName) | ||
{ | ||
JsonSchema listSchemaRef = new() | ||
{ | ||
Description = $"A page of {entityName} entities", | ||
Type = JsonObjectType.Object | ||
}; | ||
listSchemaRef.Properties["items"] = new JsonSchemaProperty | ||
{ | ||
Description = "The entities in this page of results", | ||
Type = JsonObjectType.Array, | ||
Item = entitySchema, | ||
IsReadOnly = true, | ||
IsNullableRaw = true | ||
}; | ||
listSchemaRef.Properties["count"] = new JsonSchemaProperty | ||
{ | ||
Description = "The count of all entities in the result set", | ||
Type = JsonObjectType.Integer, | ||
IsReadOnly = true, | ||
IsNullableRaw = true | ||
}; | ||
listSchemaRef.Properties["nextLink"] = new JsonSchemaProperty | ||
{ | ||
Description = "The URI to the next page of entities", | ||
Type = JsonObjectType.String, | ||
Format = "uri", | ||
IsReadOnly = true, | ||
IsNullableRaw = true | ||
}; | ||
return listSchemaRef; | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
src/CommunityToolkit.Datasync.Server.NSwag/DatasyncSchemaProcessor.cs
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,37 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using NJsonSchema; | ||
using NJsonSchema.Generation; | ||
|
||
namespace CommunityToolkit.Datasync.Server.NSwag; | ||
|
||
/// <summary> | ||
/// NSwag Schema processor for the Community Datasync Toolkit. | ||
/// </summary> | ||
public class DatasyncSchemaProcessor : ISchemaProcessor | ||
{ | ||
/// <summary> | ||
/// List of the system properties within the <see cref="ITableData"/> interface. | ||
/// </summary> | ||
private static readonly string[] systemProperties = ["deleted", "updatedAt", "version"]; | ||
|
||
/// <summary> | ||
/// Processes each schema in turn, doing required modifications. | ||
/// </summary> | ||
/// <param name="context">The schema processor context.</param> | ||
public void Process(SchemaProcessorContext context) | ||
{ | ||
if (context.ContextualType.Type.GetInterfaces().Contains(typeof(ITableData))) | ||
{ | ||
foreach (KeyValuePair<string, JsonSchemaProperty> prop in context.Schema.Properties) | ||
{ | ||
if (systemProperties.Contains(prop.Key)) | ||
{ | ||
prop.Value.IsReadOnly = true; | ||
} | ||
} | ||
} | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
src/CommunityToolkit.Datasync.Server.NSwag/GlobalSuppressions.cs
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,8 @@ | ||
// 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("Performance", "SYSLIB1045:Convert to 'GeneratedRegexAttribute'.", Justification = "<Pending>", Scope = "member", Target = "~M:CommunityToolkit.Datasync.Server.NSwag.OpenApiDatasyncExtensions.SetResponse(NSwag.OpenApiOperation,System.Net.HttpStatusCode,NJsonSchema.JsonSchema,System.Boolean)")] |
Oops, something went wrong.