Skip to content

Commit

Permalink
Add UtcNow, RandGuid support to SystemBindingData
Browse files Browse the repository at this point in the history
  • Loading branch information
MikeStall committed May 23, 2017
1 parent b064455 commit 86cb51f
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 33 deletions.
20 changes: 18 additions & 2 deletions src/Microsoft.Azure.WebJobs.Host/Bindings/BindingDataPathHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ internal static class BindingDataPathHelper
/// strings, JToken, and Guid (which is translated in canonical form without curly braces).
/// </summary>
/// <param name="parameterValue">The parameter value to convert</param>
/// <param name="format">Optional format string</param>
/// <returns>Path compatible string representation of the given parameter or null if its type is not supported.</returns>
public static string ConvertParameterValueToString(object parameterValue)
public static string ConvertParameterValueToString(object parameterValue, string format = null)
{
if (string.IsNullOrWhiteSpace(format))
{
format = null; // normalize.
}
if (parameterValue != null)
{
switch (Type.GetTypeCode(parameterValue.GetType()))
Expand All @@ -45,10 +50,21 @@ public static string ConvertParameterValueToString(object parameterValue)
return ((Byte)parameterValue).ToString(CultureInfo.InvariantCulture);
case TypeCode.SByte:
return ((SByte)parameterValue).ToString(CultureInfo.InvariantCulture);
case TypeCode.DateTime:
format = format ?? "yyyy-MM-ddTHH-mm-ssK"; // default to ISO 8601
var dateTime = (DateTime)parameterValue;
return dateTime.ToString(format, CultureInfo.InvariantCulture);
case TypeCode.Object:
if (parameterValue is Guid)
{
return parameterValue.ToString();
if (format == null)
{
return parameterValue.ToString();
}
else
{
return ((Guid)parameterValue).ToString(format, CultureInfo.InvariantCulture);
}
}
if (parameterValue is Newtonsoft.Json.Linq.JToken)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@

namespace Microsoft.Azure.WebJobs.Host.Bindings.Path
{
/// <summary>
/// Support for adding built-in values to binding data.
/// Don't add new resolvers here. Instead, add it to <see cref="SystemBindingData"/>
/// </summary>
[Obsolete("Use SystemBindingData instead")]
internal abstract class BindingParameterResolver
{
private static Collection<BindingParameterResolver> _resolvers;
Expand Down Expand Up @@ -69,6 +74,7 @@ protected string GetFormatOrNull(string value)
return null;
}

// This is an alias for 'sys.randguid'
private class RandGuidResolver : BindingParameterResolver
{
public override string Name
Expand All @@ -82,18 +88,14 @@ public override string Name
public override string Resolve(string value)
{
string format = GetFormatOrNull(value);

if (!string.IsNullOrEmpty(format))
{
return Guid.NewGuid().ToString(format, CultureInfo.InvariantCulture);
}
else
{
return Guid.NewGuid().ToString();
}
var val = new SystemBindingData().RandGuid;
return BindingDataPathHelper.ConvertParameterValueToString(val, format);
}
}

// This can't be aliases to 'sys.UtcNow' because
// 'sys.UtcNow' always resolves to DateTime.UtcNow.
// But 'datetime' may either resolve to user bidning data or to DateTime.UtcNow.
private class DateTimeResolver : BindingParameterResolver
{
public override string Name
Expand All @@ -107,16 +109,8 @@ public override string Name
public override string Resolve(string value)
{
string format = GetFormatOrNull(value);

if (!string.IsNullOrEmpty(format))
{
return DateTime.UtcNow.ToString(format, CultureInfo.InvariantCulture);
}
else
{
// default to ISO 8601
return DateTime.UtcNow.ToString("yyyy-MM-ddTHH-mm-ssK", CultureInfo.InvariantCulture);
}
var val = new SystemBindingData().UtcNow;
return BindingDataPathHelper.ConvertParameterValueToString(val, format);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,27 @@ public static BindingTemplateToken NewLiteral(string literalValue)

public static BindingTemplateToken NewExpression(string expression)
{
// BindingData takes precedence over builtins.
BindingParameterResolver builtin;
BindingParameterResolver.TryGetResolver(expression, out builtin);

// check for formatter, which is applied to finale results.
string format = null;
if (builtin == null)
{
int indexColon = expression.IndexOf(':');
if (indexColon > 0)
{
format = expression.Substring(indexColon + 1);
expression = expression.Substring(0, indexColon);
}
}

if (!BindingTemplateParser.IsValidIdentifier(expression))
{
throw new FormatException($"Invalid template expression '{expression}");
}

// BindingData takes precedence over builtins.
BindingParameterResolver builtin;
BindingParameterResolver.TryGetResolver(expression, out builtin);

// Expression is just a series of dot operators like: a.b.c
var parts = expression.Split('.');

Expand All @@ -66,7 +78,7 @@ public static BindingTemplateToken NewExpression(string expression)
}
}

return new ExpressionToken(parts, builtin);
return new ExpressionToken(parts, format, builtin);
}

public abstract string Evaluate(IReadOnlyDictionary<string, object> bindingData);
Expand Down Expand Up @@ -134,13 +146,16 @@ private class ExpressionToken : BindingTemplateToken
// If non-null, then this could be a builtin object.
private readonly BindingParameterResolver _builtin;

private readonly string _format;

// The parts of an expression, like a.b.c.
private readonly string[] _expressionParts;

public ExpressionToken(string[] expressionParts, BindingParameterResolver builtin)
public ExpressionToken(string[] expressionParts, string format, BindingParameterResolver builtin)
{
_expressionParts = expressionParts;
_builtin = builtin;
_format = format;
}

public override string AsLiteral => null;
Expand Down Expand Up @@ -198,7 +213,7 @@ public override string Evaluate(IReadOnlyDictionary<string, object> bindingData)
}
}

var strValue = BindingDataPathHelper.ConvertParameterValueToString(current);
var strValue = BindingDataPathHelper.ConvertParameterValueToString(current, _format);
return strValue;
}

Expand Down
11 changes: 11 additions & 0 deletions src/Microsoft.Azure.WebJobs.Host/Bindings/SystemBindingData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ internal class SystemBindingData
public const string Name = "sys";

// An internal name for this binding that uses characters that gaurantee it can't be overwritten by a user.
// This is never seen by the user.
// This ensures that we can always unambiguously retrieve this later.
private const string InternalKeyName = "$sys";

Expand All @@ -34,6 +35,16 @@ internal class SystemBindingData
/// </summary>
public string MethodName { get; set; }

/// <summary>
/// Get the current UTC date.
/// </summary>
public DateTime UtcNow => DateTime.UtcNow;

/// <summary>
/// Return a new random guid. This create a new guid each time it's called.
/// </summary>
public Guid RandGuid => Guid.NewGuid();

// Given a full bindingData, create a binding data with just the system object .
// This can be used when resolving default contracts that shouldn't be using an instance binding data.
internal static IReadOnlyDictionary<string, object> GetSystemBindingData(IReadOnlyDictionary<string, object> bindingData)
Expand Down
4 changes: 3 additions & 1 deletion src/Microsoft.Azure.WebJobs.Host/GlobalSuppressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,6 @@
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "2", Scope = "member", Target = "Microsoft.Azure.WebJobs.DefaultResolutionPolicy.#TemplateBind(System.Reflection.PropertyInfo,System.Attribute,Microsoft.Azure.WebJobs.Host.Bindings.Path.BindingTemplate,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.Object>)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Scope = "member", Target = "Microsoft.Azure.WebJobs.Host.PropertyHelper.#.ctor(System.Reflection.PropertyInfo)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.Azure.WebJobs.Host.Bindings.SysBindingData.#GetFromData(System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.Object>)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.Azure.WebJobs.Host.Bindings.SystemBindingData.#GetFromData(System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.Object>)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "Microsoft.Azure.WebJobs.Host.Bindings.SystemBindingData.#GetFromData(System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.Object>)")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.Azure.WebJobs.Host.Bindings.SystemBindingData.#UtcNow")]
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "Microsoft.Azure.WebJobs.Host.Bindings.SystemBindingData.#RandGuid")]
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ public void ConvertParamValueToString_IfGuidParam_ReturnsStringValue()
public void ConvertParamValueToString_IfUnupportedType_ReturnsNull()
{
// Arrange
DateTime dateTimeParam = DateTime.Now;
var obj = new { value = 12 };

// Act
string stringParamValue = BindingDataPathHelper.ConvertParameterValueToString(dateTimeParam);
string stringParamValue = BindingDataPathHelper.ConvertParameterValueToString(obj);

// Assert
Assert.Null(stringParamValue);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
Expand All @@ -9,6 +10,7 @@
using Xunit;
using Xunit.Extensions;
using Newtonsoft.Json.Linq;
using System.Globalization;

namespace Microsoft.Azure.WebJobs.Host.UnitTests.Bindings.Path
{
Expand Down Expand Up @@ -319,5 +321,46 @@ public void FromString_IgnoreCase_CreatesCaseInsensitiveTemplate()
result = template.Bind(parameters);
Assert.Equal("A/TestB/TestC", result);
}


[Fact]
public void GuidFormats()
{
var g = Guid.NewGuid();
var parameters = new Dictionary<string, object>
{
{ "g", g }
};

foreach (var format in new string[] { "N", "D", "B", "P", "X", "" })
{

BindingTemplate template = BindingTemplate.FromString(@"{g:" + format + "}");

string expected = g.ToString(format, CultureInfo.InvariantCulture);
string result = template.Bind(parameters);

Assert.Equal(expected, result);
}
}

[Fact]
public void DateTimeFormats()
{
var dt = DateTime.UtcNow;
var parameters = new Dictionary<string, object>
{
{ "dt",dt }
};


var format = "YYYYMMdd";
BindingTemplate template = BindingTemplate.FromString(@"{dt:" + format + "}");

string expected = dt.ToString(format, CultureInfo.InvariantCulture);
string result = template.Bind(parameters);

Assert.Equal(expected, result);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,32 @@ public void Initialize(ExtensionConfigContext context)
public void Test(TestJobHost<ConfigTestDefaultToMethodName> host)
{
host.Call("Func", new { k = 1 });
Assert.Equal("1", _log);
Assert.NotNull(_log);

host.Call("Func2", new { k = 1 });
Assert.Equal("Func2", _log);
}

string _log;

public void Func([Test2(Path = "{k}")] string w)
public void Func([Test2(Path = "{k}*{sys.randGuid:N}*{sys.randGuid:B}*{sys.UtcNow:yyyy}")] string w)
{
var parts = w.Split('*');
string k = parts[0];
Assert.Equal("1", k);

string guidstr1 = parts[1];
var guid1 = Guid.Parse(guidstr1);
string guidstr2 = parts[2];
var guid2 = Guid.Parse(guidstr2);

Assert.Equal(guid1.ToString("N"), guidstr1);
Assert.Equal(guid2.ToString("B"), guidstr2);
Assert.NotEqual(guid1, guid2); // each sys.RandGuid is a different value

string date = parts[3];
Assert.Equal(DateTime.UtcNow.Year.ToString(), date);

_log = w;
}

Expand Down

0 comments on commit 86cb51f

Please sign in to comment.