Skip to content
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

Closed
wants to merge 1 commit into from
Closed

WIP: Bind to stream rule #1387

wants to merge 1 commit into from

Conversation

MikeStall
Copy link
Contributor

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.

{
yield return new BindingRule
{
SourceAttribute = typeof(BlobAttribute),
Copy link

@ConnorMcMahon ConnorMcMahon Oct 17, 2017

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)

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.");
Copy link

@ConnorMcMahon ConnorMcMahon Oct 17, 2017

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

Copy link
Contributor Author

@MikeStall MikeStall Oct 17, 2017

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))
Copy link

@ConnorMcMahon ConnorMcMahon Oct 17, 2017

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:

  1. What the rule has specified (Read, Write, or ReadWrite)
  2. What the attribute specifies (Read or Write)
  3. 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Because if the rule only supports Read, then it's possible there's another rule that supports Write. IE, we can't assert it's an error. Whereas a conflict between #2 + #3 is definitely an error.


In reply to: 145215473 [](ancestors = 145215473)

return _stream;
}

public async Task InitAsync(Func<Stream> builder, Type userType)
Copy link

@ConnorMcMahon ConnorMcMahon Oct 17, 2017

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

Copy link
Contributor Author

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)

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)

Copy link
Contributor Author

@MikeStall MikeStall Oct 17, 2017

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;
Copy link

@ConnorMcMahon ConnorMcMahon Oct 17, 2017

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
Copy link

@ConnorMcMahon ConnorMcMahon Oct 17, 2017

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
Copy link

@ConnorMcMahon ConnorMcMahon Oct 17, 2017

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

Copy link
Contributor Author

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()
Copy link

@ConnorMcMahon ConnorMcMahon Oct 17, 2017

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

Copy link
Contributor Author

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)
Copy link
Contributor Author

@MikeStall MikeStall Oct 17, 2017

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)
Copy link
Member

@mathewc mathewc Oct 18, 2017

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";
Copy link
Member

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;
Copy link
Member

@mathewc mathewc Oct 18, 2017

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;

Copy link
Member

@mathewc mathewc Oct 18, 2017

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just using VS's auto-format.


In reply to: 145437932 [](ancestors = 145437932)

{
var prop = attribute.GetType().GetProperty("Access", BindingFlags.Public | BindingFlags.Instance);

bool ok = (prop != null);
Copy link
Member

@mathewc mathewc Oct 18, 2017

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;
Copy link
Member

@mathewc mathewc Oct 18, 2017

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 = "???";
Copy link
Member

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; }
Copy link
Member

@mathewc mathewc Oct 18, 2017

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();
Copy link
Member

@mathewc mathewc Oct 18, 2017

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

Copy link
Contributor Author

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);
Copy link
Member

@mathewc mathewc Oct 18, 2017

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.
@MikeStall
Copy link
Contributor Author

CR feedback is incorporated and this has been superseded by #1422

@MikeStall MikeStall closed this Nov 8, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants