Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hot Chocolate 14 Migration #2348

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
10 changes: 0 additions & 10 deletions Nuget.config
michaelstaib marked this conversation as resolved.
Show resolved Hide resolved

This file was deleted.

1 change: 1 addition & 0 deletions src/Cli/Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" />
<PackageReference Include="System.IO.Abstractions" />
<PackageReference Include="HotChocolate.Utilities.Introspection" />
</ItemGroup>

<ItemGroup>
Expand Down
66 changes: 36 additions & 30 deletions src/Cli/Exporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
namespace Cli
{
/// <summary>
/// Provides functionality for exporting GraphQL schemas, either by generating from a Azure Cosmos DB database or fetching from a GraphQL API.
/// Provides functionality for exporting GraphQL schemas, either by generating from an Azure Cosmos DB database or fetching from a GraphQL API.
/// </summary>
internal class Exporter
{
Expand Down Expand Up @@ -44,10 +44,7 @@ public static bool Export(ExportOptions options, ILogger logger, FileSystemRunti
}

// Load the runtime configuration from the file
if (!loader.TryLoadConfig(
runtimeConfigFile,
out RuntimeConfig? runtimeConfig,
replaceEnvVar: true) || runtimeConfig is null)
if (!loader.TryLoadConfig(runtimeConfigFile, out RuntimeConfig? runtimeConfig, replaceEnvVar: true))
{
logger.LogError("Failed to read the config file: {0}.", runtimeConfigFile);
return false;
Expand Down Expand Up @@ -86,14 +83,20 @@ public static bool Export(ExportOptions options, ILogger logger, FileSystemRunti
}

/// <summary>
/// Exports the GraphQL schema either by generating it from a Azure Cosmos DB database or fetching it from a GraphQL API.
/// Exports the GraphQL schema either by generating it from an Azure Cosmos DB database or fetching it from a GraphQL API.
/// </summary>
/// <param name="options">The options for exporting, including sampling mode and schema file name.</param>
/// <param name="runtimeConfig">The runtime configuration for the export process.</param>
/// <param name="fileSystem">The file system abstraction for handling file operations.</param>
/// <param name="loader">The loader for runtime configuration files.</param>
/// <param name="logger">The logger instance for logging information and errors.</param>
/// <returns>A task representing the asynchronous operation.</returns>
private static async Task ExportGraphQL(ExportOptions options, RuntimeConfig runtimeConfig, System.IO.Abstractions.IFileSystem fileSystem, FileSystemRuntimeConfigLoader loader, ILogger logger)
private static async Task ExportGraphQL(
ExportOptions options,
RuntimeConfig runtimeConfig,
IFileSystem fileSystem,
FileSystemRuntimeConfigLoader loader,
ILogger logger)
{
string schemaText;
if (options.Generate)
Expand Down Expand Up @@ -136,12 +139,12 @@ internal string ExportGraphQLFromDabService(RuntimeConfig runtimeConfig, ILogger
try
{
logger.LogInformation("Trying to fetch schema from DAB Service using HTTPS endpoint.");
schemaText = GetGraphQLSchema(runtimeConfig, useFallbackURL: false);
schemaText = GetGraphQLSchema(runtimeConfig, useFallbackUrl: false);
}
catch
{
logger.LogInformation("Failed to fetch schema from DAB Service using HTTPS endpoint. Trying with HTTP endpoint.");
schemaText = GetGraphQLSchema(runtimeConfig, useFallbackURL: true);
schemaText = GetGraphQLSchema(runtimeConfig, useFallbackUrl: true);
}

return schemaText;
Expand All @@ -151,36 +154,38 @@ internal string ExportGraphQLFromDabService(RuntimeConfig runtimeConfig, ILogger
/// Retrieves the GraphQL schema from the DAB service using either the HTTPS or HTTP endpoint based on the specified fallback option.
/// </summary>
/// <param name="runtimeConfig">The runtime configuration containing the GraphQL path and other settings.</param>
/// <param name="useFallbackURL">A boolean flag indicating whether to use the fallback HTTP endpoint. If false, the method attempts to use the HTTPS endpoint.</param>
internal virtual string GetGraphQLSchema(RuntimeConfig runtimeConfig, bool useFallbackURL = false)
/// <param name="useFallbackUrl">A boolean flag indicating whether to use the fallback HTTP endpoint. If false, the method attempts to use the HTTPS endpoint.</param>
internal virtual string GetGraphQLSchema(RuntimeConfig runtimeConfig, bool useFallbackUrl = false)
{
HttpClient client;
if (!useFallbackURL)
{
client = new( // CodeQL[SM02185] Loading internal server connection
new HttpClientHandler { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }
)
{
BaseAddress = new Uri($"https://localhost:5001{runtimeConfig.GraphQLPath}")
};
}
else
{
client = new()
{
BaseAddress = new Uri($"http://localhost:5000{runtimeConfig.GraphQLPath}")
};
}
HttpClient client = CreateIntrospectionClient(runtimeConfig.GraphQLPath, useFallbackUrl);

IntrospectionClient introspectionClient = new();
Task<HotChocolate.Language.DocumentNode> response = introspectionClient.DownloadSchemaAsync(client);
Task<HotChocolate.Language.DocumentNode> response = IntrospectionClient.IntrospectServerAsync(client);
response.Wait();

HotChocolate.Language.DocumentNode node = response.Result;

return node.ToString();
}

private static HttpClient CreateIntrospectionClient(string path, bool useFallbackUrl)
{
if (useFallbackUrl)
{
return new HttpClient { BaseAddress = new Uri($"http://localhost:5000{path}") };
}

// CodeQL[SM02185] Loading internal server connection
return new HttpClient(
new HttpClientHandler
{
ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
})
{
BaseAddress = new Uri($"https://localhost:5001{path}")
};
}

private static async Task<string> ExportGraphQLFromCosmosDB(ExportOptions options, RuntimeConfig runtimeConfig, ILogger logger)
{
// Generate the schema from Azure Cosmos DB database
Expand Down Expand Up @@ -209,6 +214,7 @@ private static async Task<string> ExportGraphQLFromCosmosDB(ExportOptions option
/// <param name="options">The options containing the output directory and schema file name.</param>
/// <param name="fileSystem">The file system abstraction for handling file operations.</param>
/// <param name="content">The schema content to be written to the file.</param>
/// <param name="logger">The logger instance for logging information and errors.</param>
private static void WriteSchemaFile(ExportOptions options, IFileSystem fileSystem, string content, ILogger logger)
{

Expand Down
10 changes: 5 additions & 5 deletions src/Config/FileSystemRuntimeConfigLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ private void OnNewFileContentsDetected(object? sender, EventArgs e)
/// <returns>True if the config was loaded, otherwise false.</returns>
public bool TryLoadConfig(
string path,
[NotNullWhen(true)] out RuntimeConfig? outConfig,
[NotNullWhen(true)] out RuntimeConfig? config,
Copy link
Collaborator Author

@michaelstaib michaelstaib Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Info] I changed the name here to bring it back into alignment with the XML docs

bool replaceEnvVar = false,
ILogger? logger = null,
bool? isDevMode = null)
Expand Down Expand Up @@ -238,7 +238,7 @@ public bool TryLoadConfig(
// mode in the new RuntimeConfig since we do not support hot-reload of the mode.
if (isDevMode is not null && RuntimeConfig.Runtime is not null && RuntimeConfig.Runtime.Host is not null)
{
// Log error when the mode is changed during hot-reload.
// Log error when the mode is changed during hot-reload.
if (isDevMode != this.RuntimeConfig.IsDevelopmentMode())
{
if (logger is null)
Expand All @@ -254,7 +254,7 @@ public bool TryLoadConfig(
RuntimeConfig.Runtime.Host.Mode = (bool)isDevMode ? HostMode.Development : HostMode.Production;
}

outConfig = RuntimeConfig;
config = RuntimeConfig;

if (LastValidRuntimeConfig is null)
{
Expand All @@ -269,7 +269,7 @@ public bool TryLoadConfig(
RuntimeConfig = LastValidRuntimeConfig;
}

outConfig = null;
config = null;
return false;
}

Expand All @@ -283,7 +283,7 @@ public bool TryLoadConfig(
logger.LogError(message: errorMessage, path);
}

outConfig = null;
config = null;
return false;
}

Expand Down
17 changes: 15 additions & 2 deletions src/Core/Authorization/GraphQLAuthorizationHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Security.Claims;
using HotChocolate.AspNetCore.Authorization;
using HotChocolate.Authorization;
michaelstaib marked this conversation as resolved.
Show resolved Hide resolved
using HotChocolate.Resolvers;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
Expand All @@ -15,7 +16,7 @@ namespace Azure.DataApiBuilder.Core.Authorization;
/// The changes in this custom handler enable fetching the ClientRoleHeader value defined within requests (value of X-MS-API-ROLE) HTTP Header.
/// Then, using that value to check the header value against the authenticated ClientPrincipal roles.
/// </summary>
public class GraphQLAuthorizationHandler : HotChocolate.AspNetCore.Authorization.IAuthorizationHandler
public class GraphQLAuthorizationHandler : IAuthorizationHandler
{
/// <summary>
/// Authorize access to field based on contents of @authorize directive.
Expand All @@ -31,7 +32,10 @@ public class GraphQLAuthorizationHandler : HotChocolate.AspNetCore.Authorization
/// Returns a value indicating if the current session is authorized to
/// access the resolver data.
/// </returns>
public ValueTask<AuthorizeResult> AuthorizeAsync(IMiddlewareContext context, AuthorizeDirective directive)
public ValueTask<AuthorizeResult> AuthorizeAsync(
IMiddlewareContext context,
AuthorizeDirective directive,
CancellationToken cancellationToken = default)
{
if (!IsUserAuthenticated(context))
{
Expand All @@ -53,6 +57,15 @@ public ValueTask<AuthorizeResult> AuthorizeAsync(IMiddlewareContext context, Aut
return new ValueTask<AuthorizeResult>(AuthorizeResult.NotAllowed);
}

// TODO : check our implementation on this.
public ValueTask<AuthorizeResult> AuthorizeAsync(
AuthorizationContext context,
IReadOnlyList<AuthorizeDirective> directives,
CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
Copy link
Collaborator Author

@michaelstaib michaelstaib Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[TODO] This needs an implementation still

}

/// <summary>
/// Get the value of the CLIENT_ROLE_HEADER HTTP Header from the HttpContext.
/// HttpContext will be present in IMiddlewareContext.ContextData
Expand Down
2 changes: 1 addition & 1 deletion src/Core/Parsers/IntrospectionInterceptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public IntrospectionInterceptor(RuntimeConfigProvider runtimeConfigProvider)
public override ValueTask OnCreateAsync(
HttpContext context,
IRequestExecutor requestExecutor,
IQueryRequestBuilder requestBuilder,
OperationRequestBuilder requestBuilder,
CancellationToken cancellationToken)
{
if (_runtimeConfigProvider.GetConfig().AllowIntrospection)
Expand Down
6 changes: 3 additions & 3 deletions src/Core/Resolvers/CosmosMutationEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ private async Task<JObject> ExecuteAsync(IMiddlewareContext context, IDictionary

ISqlMetadataProvider metadataProvider = _metadataProviderFactory.GetMetadataProvider(dataSourceName);
// If authorization fails, an exception will be thrown and request execution halts.
string graphQLType = context.Selection.Field.Type.NamedType().Name.Value;
string graphQLType = context.Selection.Field.Type.NamedType().Name;
string entityName = metadataProvider.GetEntityName(graphQLType);
AuthorizeMutation(context, queryArgs, entityName, resolver.OperationType);

Expand Down Expand Up @@ -472,12 +472,12 @@ private static void GeneratePatchOperations(JObject jObject, string currentPath,
string dataSourceName)
{
ISqlMetadataProvider metadataProvider = _metadataProviderFactory.GetMetadataProvider(dataSourceName);
string graphQLType = context.Selection.Field.Type.NamedType().Name.Value;
string graphQLType = context.Selection.Field.Type.NamedType().Name;
string entityName = metadataProvider.GetEntityName(graphQLType);
string databaseName = metadataProvider.GetSchemaName(entityName);
string containerName = metadataProvider.GetDatabaseObjectName(entityName);

string graphqlMutationName = context.Selection.Field.Name.Value;
string graphqlMutationName = context.Selection.Field.Name;
EntityActionOperation mutationOperation =
MutationBuilder.DetermineMutationOperationTypeBasedOnInputType(graphqlMutationName);

Expand Down
7 changes: 4 additions & 3 deletions src/Core/Resolvers/CosmosQueryStructure.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Azure.DataApiBuilder.Service.GraphQLBuilder;
using Azure.DataApiBuilder.Service.GraphQLBuilder.GraphQLTypes;
using Azure.DataApiBuilder.Service.GraphQLBuilder.Queries;
using HotChocolate.Execution.Processing;
using HotChocolate.Language;
using HotChocolate.Resolvers;
using Microsoft.AspNetCore.Http;
Expand Down Expand Up @@ -116,7 +117,7 @@ private static IEnumerable<LabelledColumn> GenerateQueryColumns(SelectionSetNode
[MemberNotNull(nameof(OrderByColumns))]
private void Init(IDictionary<string, object?> queryParams)
{
IFieldSelection selection = _context.Selection;
ISelection selection = _context.Selection;
ObjectType underlyingType = GraphQLUtils.UnderlyingGraphQLEntityType(selection.Field.Type);

IsPaginated = QueryBuilder.IsPaginationType(underlyingType);
Expand All @@ -127,7 +128,7 @@ private void Init(IDictionary<string, object?> queryParams)

if (fieldNode is not null)
{
Columns.AddRange(GenerateQueryColumns(fieldNode.SelectionSet!, _context.Document, SourceAlias));
Columns.AddRange(GenerateQueryColumns(fieldNode.SelectionSet!, _context.Operation.Document, SourceAlias));
}

ObjectType realType = GraphQLUtils.UnderlyingGraphQLEntityType(underlyingType.Fields[QueryBuilder.PAGINATION_FIELD_NAME].Type);
Expand All @@ -138,7 +139,7 @@ private void Init(IDictionary<string, object?> queryParams)
}
else
{
Columns.AddRange(GenerateQueryColumns(selection.SyntaxNode.SelectionSet!, _context.Document, SourceAlias));
Columns.AddRange(GenerateQueryColumns(selection.SyntaxNode.SelectionSet!, _context.Operation.Document, SourceAlias));
string typeName = GraphQLUtils.TryExtractGraphQLFieldModelName(underlyingType.Directives, out string? modelName) ?
modelName :
underlyingType.Name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,7 @@ void ProcessPaginationFields(IReadOnlyList<ISelectionNode> paginationSelections)
/// takes place which is required to fetch nested data.
/// </summary>
/// <param name="selections">Fields selection in the GraphQL Query.</param>
// TODO : This is inefficient and could lead to errors. we should rewrite this to use the ISelection API.
Copy link
Collaborator Author

@michaelstaib michaelstaib Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[TODO] This needs a rewrite on top of ISelection

private void AddGraphQLFields(IReadOnlyList<ISelectionNode> selections, RuntimeConfigProvider runtimeConfigProvider)
{
foreach (ISelectionNode node in selections)
Expand All @@ -698,7 +699,7 @@ private void AddGraphQLFields(IReadOnlyList<ISelectionNode> selections, RuntimeC
}

FragmentSpreadNode fragmentSpread = (FragmentSpreadNode)node;
DocumentNode document = _ctx.Document;
DocumentNode document = _ctx.Operation.Document;
FragmentDefinitionNode fragmentDocumentNode = document.GetNodes()
.Where(n => n.Kind == SyntaxKind.FragmentDefinition)
.Cast<FragmentDefinitionNode>()
Expand Down
Loading