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

Refactor binding pipeline to allow type info to flow through #1002

Closed
wants to merge 0 commits into from

Conversation

mathewc
Copy link
Member

@mathewc mathewc commented Nov 29, 2016

This builds on the work/investigations that @mamaso did in #871 to address #814.

I wanted to make this a deeper fix across bindings, allowing ExpandoObjects to flow into the binding layer (preserving type info).

I'm going to add a bunch of new unit tests for some of the new logic - working on those now.

{
// TODO: we should optimize this to remove the double
// serialization/deserialization
string json = ToJson(value);
Copy link
Member Author

@mathewc mathewc Nov 29, 2016

Choose a reason for hiding this comment

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

Not sure of an easier out of the box way to recursively convert from an ExpandoObject to a JObject. No worse that what we had before, but we have room for improvement.

Copy link
Member

Choose a reason for hiding this comment

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

This seems simple enough. For some reason, though, I just noticed we have a custom converter for ExpandoObject do we have a good reason for that? There's an out of the box converter with Json.NET : http://www.newtonsoft.com/json/help/html/t_newtonsoft_json_converters_expandoobjectconverter.htm

Copy link
Member Author

Choose a reason for hiding this comment

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

I wasn't aware of that converter. I'll remove our custom converter :)

return Task.CompletedTask;
}

internal static HttpResponseMessage CreateResponse(HttpRequestMessage request, object content)
Copy link
Member Author

Choose a reason for hiding this comment

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

Making this logic unit testable - I'm going to add a bunch of tests

@@ -1035,6 +1035,36 @@ public void ExcludedFunction_NotAddedToHost()
Assert.True(false, "There was no log found for duplicate calls to done");
}

[Fact]
public async Task HttpTrigger_Scenarios_Buffer()
Copy link
Member Author

Choose a reason for hiding this comment

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

Stole this test verbatim (and supporting function) from @mamaso's PR :)

Copy link
Member

@fabiocav fabiocav left a comment

Choose a reason for hiding this comment

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

A couple of small comments.

{
// TODO: we should optimize this to remove the double
// serialization/deserialization
string json = ToJson(value);
Copy link
Member

Choose a reason for hiding this comment

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

This seems simple enough. For some reason, though, I just noticed we have a custom converter for ExpandoObject do we have a good reason for that? There's an out of the box converter with Json.NET : http://www.newtonsoft.com/json/help/html/t_newtonsoft_json_converters_expandoobjectconverter.htm

statusCode = (HttpStatusCode)(int)value;
}
if ((responseObject.TryGetValue("status", out value) && value is int) ||
(responseObject.TryGetValue("statusCode", out value) && value is int))
Copy link
Member

Choose a reason for hiding this comment

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

This logic could be simplified to:
(responseObject.TryGetValue("status", out value) || responseObject.TryGetValue("statusCode", out value)) && value is int

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

{
isRawResponse = (bool)value;
}
if (responseObject.TryGetValue("isRaw", out value) && value is bool)
Copy link
Member

Choose a reason for hiding this comment

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

It's tempting to add a generic extension method to avoid those && value is <type> checks....

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

@@ -248,7 +265,7 @@ private static void AddResponseHeader(HttpResponseMessage response, KeyValuePair
}
break;
case "content-md5":
response.Content.Headers.ContentMD5 = header.Value.Value<byte[]>();
response.Content.Headers.ContentMD5 = header.Value as byte[];
Copy link
Member

Choose a reason for hiding this comment

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

A string here would be valid as well. (I believe I had a similar comment on @mamaso's PR)

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch - missed that in the other review. Fixed.

@@ -240,23 +236,6 @@ private static void EnsureInitialized()
}
}

/// <summary>
Copy link
Member

Choose a reason for hiding this comment

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

🎉 👍 😄

public static IDictionary<string, TValue> ToCaseInsensitive<TValue>(this IDictionary<string, TValue> dictionary)
{
return new Dictionary<string, TValue>(dictionary, StringComparer.OrdinalIgnoreCase);
}
Copy link
Member

Choose a reason for hiding this comment

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

Oh, just think about how nice it would be to have a TryGetValue here... 😄

Copy link
Member Author

@mathewc mathewc Nov 29, 2016

Choose a reason for hiding this comment

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

Done. Also added missing test suites + tests for new and some existing code

@mathewc mathewc changed the title Refactor binding pipeline to allow Node type info to flow through Refactor binding pipeline to allow type info to flow through Nov 29, 2016
Copy link
Contributor

@mamaso mamaso left a comment

Choose a reason for hiding this comment

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

awesome stuff!

return new Dictionary<string, TValue>(dictionary, StringComparer.OrdinalIgnoreCase);
}

public static bool TryGetValue<TValue>(this IDictionary<string, object> obj, string name, out TValue value)
Copy link
Contributor

Choose a reason for hiding this comment

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

I like it, type safe TryGetValue


public static IDictionary<string, TValue> AsCaseInsensitive<TValue>(this IDictionary<string, TValue> dictionary)
{
return new Dictionary<string, TValue>(dictionary, StringComparer.OrdinalIgnoreCase);
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this throw if there are dupe keys in the input dictionary? ie 'key' and 'KEY'. Not sure if we'd prefer to throw or just overwrite one of the props in that case

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmmm, not sure. I'll add a test and figure out the right behavior.

Copy link
Member

Choose a reason for hiding this comment

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

Good point indeed. This will surely throw an exception complaining about duplicate keys being added. I think throwing here is the right behavior, we just need to make sure this is properly handled and we provide a meaningful, detailed message from the calling code.

Copy link
Member Author

Choose a reason for hiding this comment

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

Removed the whole case insensitive thing. Modified TryGetValue helper to support case insensitive queries, which matches the logic we had before with JObject

statusCode = (HttpStatusCode)(int)value;
}
int statusValue;
if (responseObject.TryGetValue<int>("status", out statusValue) ||
Copy link
Contributor

@mamaso mamaso Nov 29, 2016

Choose a reason for hiding this comment

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

Pretty sure we need to support string for this as well :/ I remember some tests breaking when I treated status as an integer.

Seems like that cast is supported

Copy link
Member Author

Choose a reason for hiding this comment

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

All tests are passing with these changes. What is the repro you're thinking of?

Copy link
Contributor

@mamaso mamaso Nov 29, 2016

Choose a reason for hiding this comment

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

Just tried out this in prod and it gives a 400, think it will fail with this new logic.

context.res = {
        body: true,
        status: "400"
    };
context.done();

Copy link
Member Author

Choose a reason for hiding this comment

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

I've added support for both integers and strings

isRawResponse = (bool)value;
}
bool isRawValue;
if (responseObject.TryGetValue<bool>("isRaw", out isRawValue))
Copy link
Contributor

Choose a reason for hiding this comment

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

potentially same cast issue

Copy link
Member Author

@mathewc mathewc Nov 29, 2016

Choose a reason for hiding this comment

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

The previous code had a check that verified the JToken was of type Boolean, so I don't think strings were supported. However, I don't think we want to support strings for this. I could put in logic to support string values, but I don't think it's worth it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, since isRaw is so new and mainly internal I think we're good on this one

Copy link
Contributor

@mamaso mamaso left a comment

Choose a reason for hiding this comment

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

Just the one comment, tests look good too


if (!found && ignoreCase)
{
// try a case insensitive match - first match wins
Copy link
Contributor

Choose a reason for hiding this comment

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

As a small optimization we could consider special casing for first letter, i.e. TryGetValue 'body' and 'Body', I think that would catch 99% of these with no linear scan

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree that would catch most cases, but I don't think this is a big perf issue really. I'd rather not get in the business of checking a reduced set of permutations (e.g. Key, key, KEY, Key)

Copy link
Contributor

Choose a reason for hiding this comment

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

totally fair :)

@mathewc
Copy link
Member Author

mathewc commented Nov 29, 2016

Merged. I'll address any additional feedback people have.

@mathewc
Copy link
Member Author

mathewc commented Nov 29, 2016

Merged in commit 6b4c50b

@mathewc mathewc deleted the buffer-fix branch December 14, 2016 19:15
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.

None yet

5 participants