Skip to content

Commit

Permalink
Added automatic GraphQL wrapper for open api specification. (#6398)
Browse files Browse the repository at this point in the history
  • Loading branch information
A360JMaxxgamer authored Oct 8, 2023
1 parent 5982df1 commit 892bbf8
Show file tree
Hide file tree
Showing 52 changed files with 6,801 additions and 7 deletions.
4 changes: 4 additions & 0 deletions .build/Build.Tests.2.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ partial class Build
.Produces(TestResultDirectory / "*.trx")
.Executes(() => RunTests(SourceDirectory / "HotChocolate" / "Neo4J" / "HotChocolate.Neo4J.sln"));

Target TestHotChocolateOpenApi => _ => _
.Produces(TestResultDirectory / "*.trx")
.Executes(() => RunTests(SourceDirectory / "HotChocolate" / "OpenApi" / "HotChocolate.OpenApi.sln"));

Target TestHotChocolatePersistedQueries => _ => _
.Produces(TestResultDirectory / "*.trx")
.Executes(() => RunTests(SourceDirectory / "HotChocolate" / "PersistedQueries" / "HotChocolate.PersistedQueries.sln"));
Expand Down
1 change: 1 addition & 0 deletions .build/Helpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ static class Helpers
Path.Combine("HotChocolate", "Marten"),
Path.Combine("HotChocolate", "MongoDb"),
Path.Combine("HotChocolate", "Neo4J"),
Path.Combine("HotChocolate", "OpenApi"),
Path.Combine("HotChocolate", "Raven"),
Path.Combine("HotChocolate", "Skimmed"),
Path.Combine("HotChocolate", "Stitching"),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections;
using System.Text.Json;
using HotChocolate.Types;
using static HotChocolate.Execution.ErrorHelper;
using static HotChocolate.Execution.Processing.PathHelper;
Expand Down Expand Up @@ -89,6 +90,30 @@ internal static partial class ValueCompletion
return resultList;
}

if (result is JsonElement { ValueKind: JsonValueKind.Array } node)
{
var resultList = operationContext.Result.RentList(4);
resultList.IsNullable = elementType.Kind is not TypeKind.NonNull;
resultList.SetParent(parent, index);

var i = 0;
foreach (var element in node.EnumerateArray())
{
if (resultList.Count == resultList.Capacity)
{
resultList.Grow();
}

if (!TryCompleteElement(context, selection, elementType, isLeafType, resultList, i++, element))
{
operationContext.Result.AddRemovedResult(resultList);
return null;
}
}

return resultList;
}

var errorPath = CreatePathFromContext(selection, parent, index);
var error = ListValueIsNotSupported(result.GetType(), selection.SyntaxNode, errorPath);
operationContext.ReportError(error, resolverContext, selection);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public void ApplyConfiguration(

if (type.IsListType())
{
throw ThrowHelper.CannotInferTypeFromJsonObj(ctx.Type.Name);
JsonObjectTypeExtensions.InferListResolver(def);
return;
}

if (namedType is ScalarType scalarType)
Expand All @@ -46,7 +47,7 @@ public void ApplyConfiguration(
}
}

private string? GetPropertyName(DirectiveNode directive)
private static string? GetPropertyName(DirectiveNode directive)
{
if (directive.Arguments.Count == 0)
{
Expand Down
13 changes: 12 additions & 1 deletion src/HotChocolate/Core/src/Types.Json/JsonObjectTypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text.Json;
using System.Threading.Tasks;
using HotChocolate.Resolvers;
using HotChocolate.Types.Descriptors.Definitions;

Expand Down Expand Up @@ -44,7 +46,8 @@ public static IObjectFieldDescriptor FromJson(

if (type.IsListType())
{
throw ThrowHelper.CannotInferTypeFromJsonObj(ctx.Type.Name);
InferListResolver(def);
return;
}

if (namedType is ScalarType scalarType)
Expand Down Expand Up @@ -98,6 +101,11 @@ public static IObjectFieldDescriptor FromJson<TResult>(
return descriptor;
}

internal static void InferListResolver(ObjectFieldDefinition def)
{
def.PureResolver = ctx => new ValueTask<object?>(ctx.ToEnumerable());
}

internal static void InferResolver(
ITypeSystemObject type,
ObjectFieldDefinition def,
Expand Down Expand Up @@ -257,6 +265,9 @@ internal static void InferResolver(
}
}

private static IEnumerable<JsonElement> ToEnumerable(this IPureResolverContext context)
=> context.Parent<JsonElement>().EnumerateArray();

private static JsonElement? GetProperty(this IPureResolverContext context, string propertyName)
=> context.Parent<JsonElement>().TryGetProperty(propertyName, out var element)
? element
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ Accounts Subgraph Configuration
"ClientName": null,
"BaseAddress": "ws://localhost:5000/graphql"
}
]
],
"ConfigurationExtensions": null
}
---------------
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@ Accounts Subgraph Configuration
"ClientName": null,
"BaseAddress": "ws://localhost:5000/graphql"
}
]
],
"ConfigurationExtensions": null
}
---------------

Expand All @@ -218,6 +219,7 @@ Reviews2 Subgraph Configuration
"ClientName": null,
"BaseAddress": "ws://localhost:5000/graphql"
}
]
],
"ConfigurationExtensions": null
}
---------------
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ accounts Subgraph Configuration
"ClientName": null,
"BaseAddress": "https://localhost:3000/graphql"
}
]
],
"ConfigurationExtensions": null
}
---------------
10 changes: 10 additions & 0 deletions src/HotChocolate/OpenApi/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project>
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)..\'))" />

<PropertyGroup>
<TargetFrameworks>$(Library3TargetFrameworks)</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

</Project>
36 changes: 36 additions & 0 deletions src/HotChocolate/OpenApi/HotChocolate.OpenApi.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F2C06745-5F7C-4672-A905-17C8319CE67A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{2D2DC25A-E937-418B-945F-F8C07246D6B9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.OpenApi", "src\HotChocolate.OpenApi\HotChocolate.OpenApi.csproj", "{235A80D3-0696-403B-BB06-94E839C2F174}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotChocolate.OpenApi.Tests", "tests\HotChocolate.OpenApi.Tests\HotChocolate.OpenApi.Tests.csproj", "{D525025A-E91A-43F8-9322-EA0B6C835208}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{235A80D3-0696-403B-BB06-94E839C2F174} = {F2C06745-5F7C-4672-A905-17C8319CE67A}
{D525025A-E91A-43F8-9322-EA0B6C835208} = {2D2DC25A-E937-418B-945F-F8C07246D6B9}
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{235A80D3-0696-403B-BB06-94E839C2F174}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{235A80D3-0696-403B-BB06-94E839C2F174}.Debug|Any CPU.Build.0 = Debug|Any CPU
{235A80D3-0696-403B-BB06-94E839C2F174}.Release|Any CPU.ActiveCfg = Release|Any CPU
{235A80D3-0696-403B-BB06-94E839C2F174}.Release|Any CPU.Build.0 = Release|Any CPU
{D525025A-E91A-43F8-9322-EA0B6C835208}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D525025A-E91A-43F8-9322-EA0B6C835208}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D525025A-E91A-43F8-9322-EA0B6C835208}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D525025A-E91A-43F8-9322-EA0B6C835208}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
4 changes: 4 additions & 0 deletions src/HotChocolate/OpenApi/src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<Project>
<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.props', '$(MSBuildThisFileDirectory)..\'))" />
<Import Project="$([MSBuild]::GetPathOfFileAbove('Nullable.props', '$(MSBuildThisFileDirectory)..\'))" />
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using HotChocolate.OpenApi.Properties;
using HotChocolate.Skimmed;
using HotChocolate.Types;
using Microsoft.OpenApi.Models;
using INamedType = HotChocolate.Skimmed.INamedType;
using ObjectType = HotChocolate.Skimmed.ObjectType;

namespace HotChocolate.OpenApi.Helpers;

internal static class ObjectTypeFactory
{
/// <summary>
/// Parses the <paramref name="schema"/> and it fields and adds new object types
/// to context if necessary
/// </summary>
/// <param name="context"></param>
/// <param name="typeName"></param>
/// <param name="schema"></param>
/// <returns></returns>
public static INamedType ParseType(OpenApiWrapperContext context, string typeName, OpenApiSchema schema)
{
if (context.MutableSchema.Types.ContainsName(typeName))
{
return context.MutableSchema.Types[typeName];
}

if (Scalars.IsBuiltIn(typeName))
{
return new ObjectType(typeName);
}

var type = new ObjectType(typeName)
{
Description = schema.Description
};

var typeInfo = context.GetSchemaTypeInfo(schema);

foreach (var property in schema.Properties)
{
var field = CreateField(context, typeInfo.RootSchema, property);
type.Fields.Add(field);
}

foreach (var allOf in schema.AllOf)
{
foreach (var allOfProperty in allOf.Properties)
{
var field = CreateField(context, typeInfo.RootSchema, allOfProperty);
type.Fields.Add(field);
}
}

if (!context.MutableSchema.Types.ContainsName(typeName))
{
context.MutableSchema.Types.Add(type);
}

return type;
}

private static OutputField CreateField(OpenApiWrapperContext context, OpenApiSchema schema, KeyValuePair<string, OpenApiSchema> property)
{
var typeInfo = context.GetSchemaTypeInfo(property.Value);
var isRequired = schema.Required.Contains(property.Key);
var fieldType = typeInfo.GetGraphQLTypeNode(isRequired);
var field = new OutputField(OpenApiNamingHelper.GetFieldName(property.Key))
{
Type = fieldType,
Description = property.Value.Description,
ContextData =
{
[OpenApiResources.OpenApiPropertyName] = property.Key
}
};

ParseType(context, fieldType.NamedType().Name, typeInfo.RootSchema);
return field;
}
}
Loading

0 comments on commit 892bbf8

Please sign in to comment.