-
Notifications
You must be signed in to change notification settings - Fork 357
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
WIP: Bind to stream rule #1387
WIP: Bind to stream rule #1387
Conversation
{ | ||
yield return new BindingRule | ||
{ | ||
SourceAttribute = typeof(BlobAttribute), |
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.
Should BlobAttribute be more general here? #Resolved
} | ||
|
||
|
||
private static FileAccess? GetFileAccessFromAttribute(Attribute attribute) |
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 logic could be a lot cleaner if we had the guarantee that all binding Attributes that wanted to have a BindToStream rule implemented some interface along the lines of IStreamableBindingAttribute, that had a FileAccess? property guaranteed to be present.
This comes with a few downsides though, such as retrofitting older attributes to require this interface (possibly breaking changes?) and adding an extra knowledge burden on extension developers. However, avoiding reflection would be nice, and it does make sense semantically as well.
break; | ||
|
||
default: | ||
throw new NotImplementedException("ReadWrite access is not supported. Pick either Read or Write."); |
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.
Is there a reason we don't support ReadWrite access? It may make sense for future bindings, even if it doesn't for our current ones. #Resolved
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.
Our current streams are single-direction; that also greatly simplifies any marshalling.
We can always add bi-directional support later.
In reply to: 145212138 [](ancestors = 145212138)
} | ||
|
||
VerifyOrThrow(actualAccess, isRead); | ||
if (!IsSupportedByRule(isRead)) |
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 I understand this logic, we have 3 different sources of FileAccess:
- What the rule has specified (Read, Write, or ReadWrite)
- What the attribute specifies (Read or Write)
- What the parameter type supports (Read or Write).
Is there a reason that if 1 and 3 disagree (with 1 = ReadWrite agreeing with any value for 3), we return null, but if 2 and 3 disagree we throw an exception? #ByDesign
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.
return _stream; | ||
} | ||
|
||
public async Task InitAsync(Func<Stream> builder, Type userType) |
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.
Consider allowing async conversions of TAttribute to Stream, and making GetOrCreateStream() an async method, as all of its callers are async as well. #Resolved
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 believe we do support async. I called out an example in the tests.
In reply to: 145221230 [](ancestors = 145221230)
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 took a look at patternmatcher.TryGetConverterFunc() and it looks like it calls CreateConverterFunc, which uses reflection and invokers to translate the async Func<TAttribute, Task> to a synchronous Func<TAttribute, BindType>.
In reply to: 145235436 [](ancestors = 145235436,145221230)
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 would still be a separate fix in the core of the SDK. The BindToInputRule and Extension are async. #Resolved
@@ -11,6 +11,9 @@ | |||
using System.Threading; | |||
using Newtonsoft.Json; | |||
using Microsoft.Azure.WebJobs.Description; | |||
using System.IO; |
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.
Are these actually required? #Resolved
throw new InvalidOperationException(); | ||
} | ||
|
||
// Explicit Write access |
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.
Is this comment correct? #Resolved
|
||
// Explicit Write access | ||
public void Read( | ||
[TestStream(Path="{x}-%y%")] string value |
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.
Where does the %y% come from? #Resolved
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.
%...% means "take from appsettings". The test wired up an appsetting converter to substitute this.
In reply to: 145225541 [](ancestors = 145225541)
|
||
// Bulk test the success case for different parameter types we can bind to. | ||
[Fact] | ||
public void TestStream() |
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 general I don't like to have so many test cases covered by one test, but I don't see a clear way to split up the tests without adding far more classes. #Resolved
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.
Exactly :| There's so much machinery to just wire up the extension for testing; it'd be painful to repeat it or every case.
In reply to: 145228086 [](ancestors = 145228086)
|
||
#endregion // #region Write overloads | ||
|
||
public async Task<Stream> ConvertAsync(TestStreamAttribute input, CancellationToken cancellationToken) |
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.
public async Task ConvertAsync(TestStreamAttribute input [](start = 12, length = 64)
@ConnorMcMahon - here's an example of an AsyncConverter that you asked about. #Resolved
} | ||
} | ||
|
||
private void VerifyOrThrow(FileAccess? actualAccess, bool isRead) |
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.
nit: VerifyAccess? Also "actualAccess" isn't clear. Maybe "declaredAccess" since it's the attribute declared value? #Resolved
{ | ||
if (!CanRead(actualAccess.Value)) | ||
{ | ||
errorMsg = "Read"; |
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.
Can you just ToString the enum value to get the string, rather than forming these strings manually? Also, rather than passing in an "isRead" bool, why not just pass in a second FileAccess? You'd then have VerifyAccess(FileAccess? declaredAccess, FileAccess desiredAccess)
} | ||
|
||
var parameter = context.Parameter; | ||
var typeUser = parameter.ParameterType; |
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.
nit: "typeUser" isn't really clear. Why not just "parameterType"? #Resolved
{ | ||
case FileAccess.Read: | ||
isRead = true; | ||
|
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.
nit: formatting is off throughout this switch it seems #WontFix
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.
{ | ||
var prop = attribute.GetType().GetProperty("Access", BindingFlags.Public | BindingFlags.Instance); | ||
|
||
bool ok = (prop != 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.
I think your code would be clearer w/o this unecessary "ok" local. Can't you just use "prop" and set it to null if required? #Resolved
// Helper to build the stream. This will only get invoked once and then cached as _stream. | ||
private Func<Stream> _streamBuilder; | ||
|
||
object _userArg; |
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.
can you clean up these declarations (order properly and remove all the unecessary spacing)? Also, I wonder if fxcop is running - should complain that you don't have "private" access modifier. #Resolved
public async Task InitAsync(Func<Stream> builder, Type userType) | ||
{ | ||
Type = userType; | ||
_invokeString = "???"; |
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 is the right value to use here?
abstract protected Task<object> CreateUserArgAsync(); | ||
|
||
// Give derived object a chance to flush any buffering before we close the stream. | ||
virtual protected Task FlushAsync() { return Task.CompletedTask; } |
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.
nit: protected virtual #Resolved
_invokeString = "???"; | ||
_streamBuilder = builder; | ||
|
||
_userArg = await this.CreateUserArgAsync(); |
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 "user arg" naming isn't clear. Can you rename things to _value = await GetValueAsync();
? #Resolved
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'll do _userValue. by 'user', I mean value passed to the user's function (as opposed to the internal values we track)
In reply to: 145444786 [](ancestors = 145444786)
|
||
if (metadata.TryGetValue("direction", StringComparison.OrdinalIgnoreCase, out token)) | ||
// Other "look like a stream" attributes may expose an Access property for stream direction. | ||
var prop = attributeType.GetProperty("Access", BindingFlags.Instance | BindingFlags.Public); |
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.
Can we use a shared helper for this in all the 3 places I've seen this? #Resolved
Includes Script / Function.json support. verified plumbing [Blob] on top of this. Breaking changes from Blob: - When using ICloudBinderSupport<T>, when stream is missing, custom converter is not called. We just use the default value. - BOM - 'out string' does not emit a bom. Be consistent with TextWriter. - When binding to a Write Stream, 0-byte stream is still created if you write 0 bytes. Before, [Blob] would avoid creating stream.
c24f1a9
to
b45ecd0
Compare
CR feedback is incorporated and this has been superseded by #1422 |
Add a new BindToStream rule.
Resolves: #1001
This includes tests, and is verified for OneDrive. But does not yet plumb Blob on top of this.