-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathUseRavenSpatialFilteringAttributeExtensions.cs
168 lines (141 loc) · 6.62 KB
/
UseRavenSpatialFilteringAttributeExtensions.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
using System.Linq.Expressions;
using System.Reflection;
using HotChocolate.Configuration;
using HotChocolate.Data.Filters;
using HotChocolate.Data.Filters.Expressions;
using HotChocolate.Language;
using HotChocolate.Resolvers;
using HotChocolate.Types;
using HotChocolate.Types.Descriptors;
using HotChocolate.Types.Descriptors.Definitions;
using NetTopologySuite.Operation.Buffer.Validate;
using Raven.Client.Documents;
using Raven.Client.Documents.Linq;
using Raven.Client.Documents.Queries.Spatial;
using Raven.Client.Documents.Session;
namespace HotChocolate.Raven.Spatial.Filtering;
public static class UseRavenSpatialFilteringAttributeExtensions
{
public static IObjectFieldDescriptor UseRavenSpatialFiltering(this IObjectFieldDescriptor descriptor, string? scope = null)
{
descriptor
.Extend()
.OnBeforeNaming(
(c, definition) =>
{
var convention = c.GetFilterConvention(scope);
if (definition.ResultType is not { IsGenericType: true }
|| definition.ResultType.GetGenericTypeDefinition() == typeof(IRavenQueryable<>)
|| !c.TypeInspector.TryCreateTypeInfo(definition.ResultType, out var typeInfo)
)
{
throw new ArgumentException(
"FilterObjectFieldDescriptorExtensions_UseFiltering_CannotHandleType",
nameof(descriptor)
);
}
var argumentTypeReference = convention.GetFieldType(typeInfo.NamedType);
definition.Configurations.Add(
new CompleteConfiguration<ObjectFieldDefinition>(
(ctx, d) =>
CompileMiddleware(ctx, d, argumentTypeReference, scope),
definition,
ApplyConfigurationOn.Completion,
argumentTypeReference,
TypeDependencyKind.Completed
)
);
}
);
return descriptor;
}
private static RavenFilterVisitor Visitor { get; } = new(new QueryableCombinator());
private static readonly MethodInfo _factoryTemplate =
typeof(UseRavenSpatialFilteringAttributeExtensions)
.GetMethod(nameof(CreateExecutor), BindingFlags.Static | BindingFlags.NonPublic)!;
private static void CompileMiddleware(
ITypeCompletionContext context,
ObjectFieldDefinition definition,
ITypeReference argumentTypeReference,
string? scope
)
{
var type = context.GetType<IFilterInputType>(argumentTypeReference);
var convention = context.DescriptorContext.GetFilterConvention(scope);
var factory = _factoryTemplate.MakeGenericMethod(type.EntityType.Source);
var middleware = (FieldMiddleware)factory.Invoke(null, new object[] { convention.GetArgumentName() })!;
var filteringMiddleware = definition.MiddlewareDefinitions.SingleOrDefault(z => z.Key == WellKnownMiddleware.Filtering);
if (filteringMiddleware is null)
{
throw new NotSupportedException("Filtering middleware is required");
}
var index = definition.MiddlewareDefinitions.IndexOf(filteringMiddleware);
definition.MiddlewareDefinitions.Insert(index + 1, new(middleware, key: "HotChocolate.Raven.Spatial.Filtering"));
}
private static ApplyFiltering CreateApplicatorAsync<TEntityType>(NameString argumentName)
{
return (context, input) =>
{
// next we get the filter argument. If the filter argument is already on the context
// we use this. This enabled overriding the context with LocalContextData
var argument = context.Selection.Field.Arguments[argumentName];
var filter = context.LocalContextData.ContainsKey(QueryableFilterProvider.ContextValueNodeKey) &&
context.LocalContextData[QueryableFilterProvider.ContextValueNodeKey] is IValueNode node
? node
: context.ArgumentLiteral<IValueNode>(argumentName);
if (filter.IsNull())
{
return input;
}
if (argument.Type is IFilterInputType filterInput)
{
var visitorContext = VisitFilterArgumentExecutor(filter, filterInput, false);
if (filter is ObjectValueNode { Fields.Count: 0 })
{
context.LocalContextData = context.LocalContextData.SetItem(QueryableFilterProvider.SkipFilteringKey, true);
}
if (visitorContext is
{
SpatialExpression: { } spatialExpression,
SpatialFieldName: { } spatialFieldName,
FieldName: { } fieldName
}
&& input is IRavenQueryable<TEntityType> ravenQueryable and IRavenQueryInspector ravenQueryInspector)
{
return ravenQueryInspector.IndexName == null
? // dynamic query
ravenQueryable.Spatial(x => spatialFieldName, spatialExpression.Compile())
: // index query
ravenQueryable.Spatial(fieldName, spatialExpression.Compile());
}
}
if (filter is ObjectValueNode { Fields.Count: 0 })
{
context.LocalContextData = context.LocalContextData.SetItem(QueryableFilterProvider.SkipFilteringKey, true);
}
return input;
};
}
private static RavenQueryableFilterContext VisitFilterArgumentExecutor(
IValueNode valueNode,
IFilterInputType filterInput,
bool inMemory
)
{
var visitorContext = new RavenQueryableFilterContext(filterInput, inMemory);
// rewrite GraphQL input object into expression tree.
Visitor.Visit(valueNode, visitorContext);
return visitorContext;
}
private static FieldMiddleware CreateExecutor<TEntityType>(NameString argumentName)
{
var applyFilter = CreateApplicatorAsync<TEntityType>(argumentName);
return next => context => ExecuteAsync(next, context);
async ValueTask ExecuteAsync(FieldDelegate next, IMiddlewareContext context)
{
// first we let the pipeline run and produce a result.
await next(context).ConfigureAwait(false);
context.Result = applyFilter(context, context.Result);
}
}
}