Skip to content

Commit

Permalink
Convention Extensions (#2451)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Staib <michael@chillicream.com>
  • Loading branch information
PascalSenn and michaelstaib committed Oct 18, 2020
1 parent 5360b70 commit 5c419eb
Show file tree
Hide file tree
Showing 53 changed files with 2,867 additions and 280 deletions.
Expand Up @@ -10,6 +10,32 @@ namespace HotChocolate
{
public static partial class SchemaBuilderExtensions
{
public static ISchemaBuilder AddConvention<T>(
this ISchemaBuilder builder,
Type type,
string? scope = null)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}

return builder.AddConvention(typeof(T), type, scope);
}

public static ISchemaBuilder AddConvention<T>(
this ISchemaBuilder builder,
CreateConvention conventionFactory,
string? scope = null)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}

return builder.AddConvention(typeof(T), conventionFactory, scope);
}

public static ISchemaBuilder AddConvention(
this ISchemaBuilder builder,
Type convention,
Expand Down Expand Up @@ -231,6 +257,13 @@ public static partial class SchemaBuilderExtensions
where T : IConvention =>
builder.TryAddConvention(typeof(T), conventionFactory, scope);

public static ISchemaBuilder TryAddConvention<T>(
this ISchemaBuilder builder,
Type type,
string? scope = null)
where T : IConvention =>
builder.TryAddConvention(typeof(T), type, scope);

public static ISchemaBuilder TryAddConvention<T>(
this ISchemaBuilder builder,
IConvention convention,
Expand Down
4 changes: 3 additions & 1 deletion src/HotChocolate/Core/src/Types/InternalsVisibleTo.cs
Expand Up @@ -10,4 +10,6 @@
[assembly: InternalsVisibleTo("HotChocolate.Types.Selections")]
[assembly: InternalsVisibleTo("HotChocolate.Types.Filters.Tests")]
[assembly: InternalsVisibleTo("HotChocolate.Types.Sorting.Tests")]
[assembly: InternalsVisibleTo("HotChocolate.AspNetCore.Tests")]
[assembly: InternalsVisibleTo("HotChocolate.AspNetCore.Tests")]
[assembly: InternalsVisibleTo("HotChocolate.Data.Filters.Tests")]
[assembly: InternalsVisibleTo("HotChocolate.Data.Sorting.Tests")]
13 changes: 9 additions & 4 deletions src/HotChocolate/Core/src/Types/SchemaBuilder.cs
Expand Up @@ -30,8 +30,8 @@ public partial class SchemaBuilder : ISchemaBuilder
new Dictionary<OperationType, CreateRef>();
private readonly Dictionary<FieldReference, FieldResolver> _resolvers =
new Dictionary<FieldReference, FieldResolver>();
private readonly Dictionary<(Type, string), CreateConvention> _conventions =
new Dictionary<(Type, string), CreateConvention>();
private readonly Dictionary<(Type, string), List<CreateConvention>> _conventions =
new Dictionary<(Type, string), List<CreateConvention>>();
private readonly Dictionary<Type, (CreateRef, CreateRef)> _clrTypes =
new Dictionary<Type, (CreateRef, CreateRef)>();
private readonly List<object> _schemaInterceptors = new List<object>();
Expand Down Expand Up @@ -190,8 +190,13 @@ public ISchemaBuilder AddType(Type type)
throw new ArgumentNullException(nameof(convention));
}

_conventions[(convention, scope)] = factory ??
throw new ArgumentNullException(nameof(factory));
if(!_conventions.TryGetValue((convention, scope), out List<CreateConvention> factories))
{
factories = new List<CreateConvention>();
_conventions[(convention, scope)] = factories;
}

factories.Add(factory);

return this;
}
Expand Down
Expand Up @@ -21,6 +21,7 @@ protected set
throw new InvalidOperationException(
"The convention scope is immutable.");
}

_scope = value;
}
}
Expand All @@ -32,6 +33,10 @@ protected internal virtual void Initialize(IConventionContext context)
MarkInitialized();
}

protected internal virtual void OnComplete(IConventionContext context)
{
}

protected void MarkInitialized()
{
if (IsInitialized)
Expand Down
Expand Up @@ -8,20 +8,15 @@ namespace HotChocolate.Types.Descriptors
internal sealed class ConventionContext : IConventionContext
{
public ConventionContext(
IConvention convention,
string? scope,
IServiceProvider services,
IDescriptorContext descriptorContext)
{
Convention = convention;
Scope = scope;
Services = services;
DescriptorContext = descriptorContext;
}

/// <inheritdoc />
public IConvention Convention { get; }

/// <inheritdoc />
public string? Scope { get; }

Expand All @@ -34,10 +29,13 @@ internal sealed class ConventionContext : IConventionContext
/// <inheritdoc />
public IDescriptorContext DescriptorContext { get; }

/// <inheritdoc />
public void ReportError(ISchemaError error)
{
throw new NotImplementedException();
}
public static ConventionContext Create(
string? scope,
IServiceProvider services,
IDescriptorContext descriptorContext) =>
new ConventionContext(
scope,
services,
descriptorContext);
}
}
@@ -0,0 +1,14 @@
namespace HotChocolate.Types.Descriptors
{
public abstract class ConventionExtension
: Convention
, IConventionExtension
{
public abstract void Merge(IConventionContext context, Convention convention);

protected internal sealed override void OnComplete(IConventionContext context)
{
base.OnComplete(context);
}
}
}
@@ -0,0 +1,10 @@
namespace HotChocolate.Types.Descriptors
{
public abstract class ConventionExtension<TDefinition>
: Convention<TDefinition>,
IConventionExtension
where TDefinition : class
{
public abstract void Merge(IConventionContext context, Convention convention);
}
}
Expand Up @@ -10,21 +10,26 @@ public abstract class Convention<TDefinition> : Convention
{
private TDefinition? _definition;

protected virtual TDefinition? Definition
{
get
{
return _definition;
}
}

protected internal sealed override void Initialize(IConventionContext context)
{
AssertUninitialized();

Scope = context.Scope;
_definition = CreateDefinition(context);

OnComplete(context, _definition);

MarkInitialized();
_definition = null;
}

protected virtual void OnComplete(IConventionContext context, TDefinition definition)
protected internal override void OnComplete(IConventionContext context)
{
_definition = null;
}

protected abstract TDefinition CreateDefinition(IConventionContext context);
Expand Down
Expand Up @@ -12,7 +12,10 @@ public sealed class DescriptorContext : IDescriptorContext
{
private readonly Dictionary<(Type, string?), IConvention> _conventions =
new Dictionary<(Type, string?), IConvention>();
private readonly IReadOnlyDictionary<(Type, string?), CreateConvention> _convFactories;

private readonly IReadOnlyDictionary<(Type, string?), List<CreateConvention>>
_convFactories;

private readonly IServiceProvider _services;

private INamingConventions? _naming;
Expand All @@ -22,7 +25,7 @@ public sealed class DescriptorContext : IDescriptorContext

private DescriptorContext(
IReadOnlySchemaOptions options,
IReadOnlyDictionary<(Type, string?), CreateConvention> convFactories,
IReadOnlyDictionary<(Type, string?), List<CreateConvention>> convFactories,
IServiceProvider services,
IDictionary<string, object?> contextData,
SchemaBuilder.LazySchema schema,
Expand Down Expand Up @@ -57,6 +60,7 @@ public INamingConventions Naming
_naming = GetConventionOrDefault<INamingConventions>(
() => new DefaultNamingConventions(Options.UseXmlDocumentation));
}

return _naming;
}
}
Expand All @@ -70,6 +74,7 @@ public ITypeInspector TypeInspector
_inspector = this.GetConventionOrDefault<ITypeInspector>(
new DefaultTypeInspector());
}

return _inspector;
}
}
Expand All @@ -90,69 +95,104 @@ public ITypeInspector TypeInspector
throw new ArgumentNullException(nameof(defaultConvention));
}

if (!TryGetConvention(scope, out T? convention))
if (_conventions.TryGetValue((typeof(T), scope), out IConvention? conv) &&
conv is T castedConvention)
{
return castedConvention;
}

CreateConventions<T>(
scope,
out IConvention? createdConvention,
out IList<IConventionExtension>? extensions);

createdConvention ??= createdConvention as T;
createdConvention ??= _services.GetService(typeof(T)) as T;
createdConvention ??= defaultConvention();

if (createdConvention is Convention init)
{
convention = _services.GetService(typeof(T)) as T;
ConventionContext conventionContext =
ConventionContext.Create(scope, _services, this);

init.Initialize(conventionContext);
MergeExtensions(conventionContext, init, extensions);
init.OnComplete(conventionContext);
}

if (convention is null)
if (createdConvention is T createdConventionOfT)
{
convention = defaultConvention();
_conventions[(typeof(T), scope)] = createdConventionOfT;
return createdConventionOfT;
}

return convention;
throw ThrowHelper.Convention_ConventionCouldNotBeCreated(typeof(T), scope);
}

private bool TryGetConvention<T>(
private void CreateConventions<T>(
string? scope,
[NotNullWhen(true)] out T? convention)
where T : class, IConvention
out IConvention? createdConvention,
out IList<IConventionExtension> extensions)
{
if (_conventions.TryGetValue(
(typeof(T), scope), out IConvention? conv))
{
if (conv is T casted)
{
convention = casted;
return true;
}
}
createdConvention = null;
extensions = new List<IConventionExtension>();

if (_convFactories.TryGetValue(
(typeof(T), scope),
out CreateConvention? factory))
out List<CreateConvention>? factories))
{
conv = factory(_services);
if (conv is Convention init)
for (var i = 0; i < factories.Count; i++)
{
var conventionContext = new ConventionContext(init, scope, _services, this);
init.Initialize(conventionContext);
_conventions[(typeof(T), scope)] = init;
IConvention conv = factories[i](_services);
if (conv is IConventionExtension extension)
{
extensions.Add(extension);
}
else
{
if (createdConvention is not null)
{
throw ThrowHelper.Convention_TwoConventionsRegisteredForScope(
typeof(T),
createdConvention,
conv,
scope);
}

createdConvention = conv;
}
}
}
}

if (conv is T casted)
private static void MergeExtensions(
IConventionContext context,
Convention convention,
IList<IConventionExtension> extensions)
{
for (var m = 0; m < extensions.Count; m++)
{
if (extensions[m] is Convention extensionConvention)
{
convention = casted;
return true;
extensionConvention.Initialize(context);
extensions[m].Merge(context, convention);
extensionConvention.OnComplete(context);
}
}

convention = default;
return false;
}

internal static DescriptorContext Create(
IReadOnlySchemaOptions? options = null,
IServiceProvider? services = null,
IReadOnlyDictionary<(Type, string?), CreateConvention>? conventions = null,
IReadOnlyDictionary<(Type, string?), List<CreateConvention>>? conventions = null,
IDictionary<string, object?>? contextData = null,
SchemaBuilder.LazySchema? schema = null,
ISchemaInterceptor? schemaInterceptor = null,
ITypeInterceptor? typeInterceptor = null)
{
return new DescriptorContext(
options ?? new SchemaOptions(),
conventions ?? new Dictionary<(Type, string?), CreateConvention>(),
conventions ?? new Dictionary<(Type, string?), List<CreateConvention>>(),
services ?? new EmptyServiceProvider(),
contextData ?? new Dictionary<string, object?>(),
schema ?? new SchemaBuilder.LazySchema(),
Expand Down
Expand Up @@ -10,11 +10,6 @@ namespace HotChocolate.Types.Descriptors
/// </summary>
public interface IConventionContext : IHasScope
{
/// <summary>
/// The convention that is being initialized.
/// </summary>
IConvention Convention { get; }

/// <summary>
/// The schema level services.
/// </summary>
Expand All @@ -31,13 +26,5 @@ public interface IConventionContext : IHasScope
/// The descriptor context that is passed through the initialization process.
/// </summary>
IDescriptorContext DescriptorContext { get; }

/// <summary>
/// Report a schema initialization error.
/// </summary>
/// <param name="error">
/// The error that occurred during initialization.
/// </param>
void ReportError(ISchemaError error);
}
}

0 comments on commit 5c419eb

Please sign in to comment.