-
Notifications
You must be signed in to change notification settings - Fork 356
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
Improving Table filter parameter validation #984
Conversation
@@ -79,7 +80,24 @@ public static IBindingProvider Build(INameResolver nameResolver, IConverterManag | |||
var bindAsyncCollector = bindingFactory.BindToAsyncCollector<TableAttribute, ITableEntity>(original.BuildFromTableAttribute, null, original.CollectAttributeInfo); | |||
|
|||
var bindToJobject = bindingFactory.BindToExactAsyncType<TableAttribute, JObject>(original.BuildJObject, null, original.CollectAttributeInfo); | |||
var bindToJArray = bindingFactory.BindToExactAsyncType<TableAttribute, JArray>(original.BuildJArray, null, original.CollectAttributeInfo); | |||
|
|||
BindingTemplate bindingTemplate = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to figure out state management. There is 1 failing test and it's because I think this local is leaked incorrectly. The delegate model for adding hooks makes it hard to figure out how to do state management like this. How can I capture the BindingTemplate once in my "post resolve hook" and make that available in all further invocations of my "pre bind hook"?
|
||
protected override Task<IValueProvider> BuildAsync(TAttribute attrResolved, BindingContext context) | ||
{ | ||
_preBindHook?.Invoke(attrResolved, context); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've only plumbed this through ExactBinding, not the other binding types. If we agree on expanding the hooks like this (I think we need to anyways) we need to do it a cross the board.
Loose delegates like this as an extensibility model make it cumbersome for binding implementations to hook in. As the number of hook points increases, I think it'll get worse. They really need to be specifying a custom type defining the contract, allowing them to override only the bits they need.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if you had the BindingContext passed into the BindToExactType
delegate? Would you need the post-resolve/pre-bind hooks?
I don't like these hooks either -- I've been asking Mike if we can reduce them to keep the concept count lower. I think
with his newest changes we'll be closer to the 'custom type' model that you're proposing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure I understand - BindingContext is only available at invoke time, whereas up to this point all of the other binding logic (other than converters) was index time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't resolving parameters like {id}
happen at invoke time? You're doing it inside the pre-bind hook right now. I'm trying to think through the pattern for people that want to do custom binding and not use [AutoResolve]
-- which we'll need to do for the DocDB SqlQuery feature as well.
You brought it up elsewhere already -- you want to construct the template during index time, which explains the need for a 'post-resolve hook'. But I'm curious whether you'd need a 'pre-bind hook' if we just passed the BindingContext directly to the 'rule' delegate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see what you mean by the "rule" delegate - I think you're right - if I just change the signature there that would work.
string value = null; | ||
if (convertedBindingData.TryGetValue(parameterName, out value)) | ||
{ | ||
if (isStringLiteral) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also escape DateTimes. (https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/formatting-datetime-property-values )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Formatting DateTimes when we have the type info as ISO is a good idea. I've added formatting for DateTime and DateTimeOffset. See new if block below.
|
||
protected override Task<IValueProvider> BuildAsync(TAttribute attrResolved, BindingContext context) | ||
{ | ||
_preBindHook?.Invoke(attrResolved, context); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if you had the BindingContext passed into the BindToExactType
delegate? Would you need the post-resolve/pre-bind hooks?
I don't like these hooks either -- I've been asking Mike if we can reduce them to keep the concept count lower. I think
with his newest changes we'll be closer to the 'custom type' model that you're proposing.
if (!string.IsNullOrEmpty(attribute.Filter)) | ||
{ | ||
bindingTemplate = BindingTemplate.FromString(attribute.Filter); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could the bindingTemplate creation be in the pre-bind hook? You've got the attribute there, which it looks like all you need. That would keep the bindingTemplate logic in one place rather than splitting it up between these two hooks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parsing and validation of a binding template needs to be an index time operation - you want to get errors early. All existing bindings not using BindingFactory do this when the binding is created, not on the invoke path. That's what I need to replicate. The logic is split intentionally - validate and parse, and on the invoke path use/reuse.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense, but the API doesn't make that very clear. I brought this up in another review with two other concepts: "Filter" and "Validator". One runs at indexing time and the other runs per-invocation. It's not clear which is which. I think we do need to reconcile all of this to have some cohesive naming so it's clear where you do certain things. That's doesn't really apply to this PR, though -- this just helps illustrate the point :-)
object originalValue; | ||
if (bindingData.TryGetValue(parameterName, out originalValue)) | ||
{ | ||
if (originalValue is DateTime) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (originalValue is DateTime) [](start = 20, length = 30)
originalValue's type may often be a string - especially if this is coming in from an untyped or query string parameter. The way we know to format as a date time is from the Odata string context. Ie, "Prop eq datetime'{x}'" is a datetime, but "Prop eq '{x}'" is a string literal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we have this same issue with guid and binary - although those are likely rarer cases.
In reply to: 98048689 [](ancestors = 98048689)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, guid is already handled. Guid formatting is just ToString on the guid (that's what Azure SDK does). We already do this. For Binary, that is too complicated, and I think we should leave that to the user. I.e., if they have a byte[] property, we make them do the base 64. I don't think we should concern ourselves with that right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, for this type formatting, we're only helping when we have the type info. That will always be the case for core SDK. For dynamic scenarios like Azure Functions non-C#, yes these will be strings, so for now we expect users to format those values. That story can improve in the future with an AF type model for trigger values.
@@ -79,7 +80,24 @@ public static IBindingProvider Build(INameResolver nameResolver, IConverterManag | |||
var bindAsyncCollector = bindingFactory.BindToAsyncCollector<TableAttribute, ITableEntity>(original.BuildFromTableAttribute, null, original.CollectAttributeInfo); | |||
|
|||
var bindToJobject = bindingFactory.BindToExactAsyncType<TableAttribute, JObject>(original.BuildJObject, null, original.CollectAttributeInfo); | |||
var bindToJArray = bindingFactory.BindToExactAsyncType<TableAttribute, JArray>(original.BuildJArray, null, original.CollectAttributeInfo); | |||
|
|||
BindingTemplate bindingTemplate = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bindingTemplate [](start = 28, length = 15)
I don't think we should have any changes in this file. This is controlling { } resolution, so it feels like the change should be in attribute cloner. There shouldn't be any hooks needed. I'll get you a specific proposal.
// Expand on [AutoResolve] property to make this pluggable, | ||
public static ResolutionPolicy GetPolicy(PropertyInfo propInfo) | ||
{ | ||
if (propInfo.DeclaringType == typeof(TableAttribute) && propInfo.Name == "Filter") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume the plan is that we open this up when I need it for DocDB?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. This is a a highly tactical change to get Table.Filter in quickly. Note that we could revert a bunch of files around bindings, bindng provider, flow the delegates, etc - those changes are more complex and not necessary to fix table.filter.
In reply to: 98059901 [](ancestors = 98059901)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this PR I think we should just put an attribute in place allowing this to be specified rather than be hardcoded like this. Kind of like what I did for ParameterResolver. We can still iterate on it, but it would be good to put what we think we want in place now.
var filter = TableFilterFormatter.Format(template, bindingData); | ||
return filter; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happened to the 'Properties' bag? I'm still going to need that for DocDB to pass data from the template binding to my binding rules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All that should be done in a separate work item I think. You'll be most of the way there with this work.
// datetime filter range | ||
bindingData = new Dictionary<string, object> | ||
{ | ||
{ "D1", "2000-01-10" }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"2000-01-10" [](start = 28, length = 13)
One of these should be in a different date format just to exercise that we're actually doing the date formatting and just passing the strings through directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For string literals, we're not doing any formatting. We only do that when we have type info.
Merged. Any additional feedback or requested changes we can deal with in follow up PRs as needed. |
No description provided.