joao-figueira
joao-figueira

Reputation: 95

Add filtering to schemas created dynamically with hotchocolate (GraphQL)

I'm using the new interface (ITypeModule) introduced on the latest version of the hotchocolate framework (v12) to dynamically create all types of my schema. (https://chillicream.com/blog/2021/09/27/hot-chocolate-12#dynamic-schemas)

It's working well. But now I'm struggling to find out how to add filtering on my types using this stategy (since I can't use anotations and neither the descriptor like its done on the documentation (https://chillicream.com/docs/hotchocolate/fetching-data/filtering)

What I've tried so far:

My dependency injection:

// code omitted 
builder.Services
       .AddGraphQLServer()
       .AddTypeModule<DynamicTenantSchemaTypeModule>()
       .AddFiltering();

On the ITypeModule implementation (DynamicTenantSchemaTypeModule):

    //code omitted
    public async ValueTask<IReadOnlyCollection<ITypeSystemMember>> CreateTypesAsync(IDescriptorContext context, CancellationToken cancellationToken)
    {
            var types = new List<ITypeSystemMember>();
            var queryType = new ObjectTypeDefinition("Query");
            var schemas = await _documentSchemaRepository.GetSchemasAsync();
            foreach (var schema in schemas)
            {
                var schemaNamePascalCase = schema.Name!.ToPascalCase();
                var schemaNamePluralCamelCase = schema.PluralName!.ToCamelCase();
                var objectTypeDefinition = new ObjectTypeDefinition(schemaNamePascalCase);
                await AddFieldsAsync(types, schema, objectTypeDefinition, schema.Properties);
                queryType.Fields.Add(new ObjectFieldDefinition(schemaNamePluralCamelCase)
                {
                    Type = TypeReference.Parse($"[{schemaNamePascalCase}]"),
                    Resolver = async (ctx) =>
                    {
                        var documents = await _documentRepository.GetDocumentsAsync(schema.Id);
                        return documents;
                    }
                }
                .ToDescriptor(context)
                .UseFiltering()
                .ToDefinition());
                types.Add(ObjectType.CreateUnsafe(objectTypeDefinition));
            }
            types.Add(ObjectType.CreateUnsafe(queryType));
            return types;
}
//code omitted

But it throws the following exception:

HotChocolate.SchemaException: For more details look at the `Errors` property.

1. No default filter convention found. Call `AddFiltering()` on the schema builder.

   at HotChocolate.Data.FilterDescriptorContextExtensions.<>c__DisplayClass1_0.<GetFilterConvention>b__0()
   at HotChocolate.Types.Descriptors.DescriptorContext.GetConventionOrDefault[T](Func`1 defaultConvention, String scope)
   at HotChocolate.Data.FilterDescriptorContextExtensions.GetFilterConvention(IDescriptorContext context, String scope)
   at HotChocolate.Types.FilterObjectFieldDescriptorExtensions.<>c__DisplayClass5_0.<UseFiltering>b__1(IDescriptorContext c, ObjectFieldDefinition definition)
   at HotChocolate.Types.Descriptors.DescriptorBase`1.<>c__DisplayClass19_0.<OnBeforeCreate>b__0(IDescriptorContext c, IDefinition d)
   at HotChocolate.Types.Descriptors.DescriptorBase`1.CreateDefinition()
   at HotChocolate.Types.Descriptors.DescriptorExtensions.ToDefinition[T](IDescriptor`1 descriptor)

Any ideas on how to add the filtering middleware correctly? Thanks!

Upvotes: 2

Views: 2552

Answers (1)

Karel Frajt&#225;k
Karel Frajt&#225;k

Reputation: 4489

So far I got the filtering work on some field which I don't know where it comes from

  1. apply filter convention in your code with configuration
descriptor.UseFiltering<MyFilterConvention>(f =>
{
  f.Name(schemaNamePascalCase + "OperationFilterInput");
});
  1. create your custom filter convention MyFilterConvention by deriving from HotChocolate.Data.Filters.FilterConvention class
  2. configure the convention to add a provider:
protected override void Configure(IFilterConventionDescriptor descriptor)
{
  descriptor.AddDefaults();
  descriptor.Provider(new QueryableFilterProvider(x => 
    x.AddDefaultFieldHandlers()));
}

This is the result for object Foo

foos (
    where: FooOperationFilterInput
): [Foo]

input FooOperationFilterInput {
  and: [FooOperationFilterInput!]
  or: [FooOperationFilterInput!]
  scope: StringOperationFilterInput
}

input StringOperationFilterInput {
  and: [StringOperationFilterInput!]
  or: [StringOperationFilterInput!]
  eq: String
  neq: String
  ... the rest is omitted for brevity ...
}

Upvotes: 1

Related Questions