Skip to content

Commit

Permalink
Merge pull request #229 from gigya/develop
Browse files Browse the repository at this point in the history
Throw InvalidParameterValueException in case parameter value is invalid
  • Loading branch information
bronsh authored Dec 27, 2018
2 parents 6b3aa21 + d645417 commit 5187a80
Show file tree
Hide file tree
Showing 15 changed files with 316 additions and 38 deletions.
18 changes: 16 additions & 2 deletions Gigya.Microdot.Hosting/HttpService/HttpServiceListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
using Gigya.Microdot.SharedLogic.HttpService;
using Gigya.Microdot.SharedLogic.Measurement;
using Gigya.Microdot.SharedLogic.Security;
using Gigya.ServiceContract.Exceptions;
using Metrics;
using Newtonsoft.Json;

Expand Down Expand Up @@ -554,8 +555,21 @@ private static object[] GetParametersByName(ServiceMethod serviceMethod, IDictio
{
return serviceMethod.ServiceInterfaceMethod
.GetParameters()
.Select(p => JsonHelper.ConvertWeaklyTypedValue(args[p.Name], p.ParameterType))
.ToArray();
.Select(p =>
{
try
{
return JsonHelper.ConvertWeaklyTypedValue(args[p.Name], p.ParameterType);
}
catch (InvalidParameterValueException ex)
{
if (ex.parameterName != null)
throw;

throw new InvalidParameterValueException(p.Name, ex.ErrorPath, ex.Message, ex);
}
}).
ToArray();
}

internal static HttpStatusCode GetExceptionStatusCode(Exception exception)
Expand Down
1 change: 0 additions & 1 deletion Gigya.Microdot.Ninject/RegexTimeoutInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ public void Init()
}

AppDomain.CurrentDomain.SetData("REGEX_DEFAULT_MATCH_TIMEOUT",TimeSpan.FromMilliseconds(regexDefaultMachTimeOutMs));
Console.WriteLine($"REGEX_DEFAULT_MATCH_TIMEOUT is set to {regexDefaultMachTimeOutMs} ms");
}
}
}
24 changes: 12 additions & 12 deletions Gigya.Microdot.SharedLogic/Exceptions/StackTraceEnhancer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@ public StackTraceEnhancer(Func<StackTraceEnhancerSettings> getConfig, IEnvironme

public JObject ToJObjectWithBreadcrumb(Exception exception)
{
var breadcrumb = new Breadcrumb
{
ServiceName = CurrentApplicationInfo.Name,
ServiceVersion = CurrentApplicationInfo.Version.ToString(),
HostName = CurrentApplicationInfo.HostName,
DataCenter = Environment.Zone,
DeploymentEnvironment = Environment.DeploymentEnvironment
};

if (exception is SerializableException serEx)
serEx.AddBreadcrumb(breadcrumb);

var jobject = JObject.FromObject(exception, Serializer);

if (GetConfig().Enabled == false)
Expand All @@ -47,18 +59,6 @@ public JObject ToJObjectWithBreadcrumb(Exception exception)
breadcrumbTarget = jobject.Property("StackTraceString");
}

var breadcrumb = new Breadcrumb
{
ServiceName = CurrentApplicationInfo.Name,
ServiceVersion = CurrentApplicationInfo.Version.ToString(),
HostName = CurrentApplicationInfo.HostName,
DataCenter = Environment.Zone,
DeploymentEnvironment = Environment.DeploymentEnvironment
};

if (exception is SerializableException serEx)
serEx.AddBreadcrumb(breadcrumb);

breadcrumbTarget.Value = $"\r\n--- End of stack trace from {breadcrumb} ---\r\n{breadcrumbTarget.Value}";

return jobject;
Expand Down
54 changes: 54 additions & 0 deletions Gigya.ServiceContract/Exceptions/InvalidParameterValueException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System;
using System.Runtime.Serialization;
using Gigya.Common.Contracts.Exceptions;
using Newtonsoft.Json;

namespace Gigya.ServiceContract.Exceptions
{
/// <summary>
/// This excpetion is thrown if a parameter contains an invalid value
/// </summary>
[Serializable]
public class InvalidParameterValueException: RequestException
{
///<summary>ErrorCode of Invalid_parameter_value</summary>
public override int? ErrorCode => 400006;

/// <summary>
/// Name of the parameter which has an invalid value
/// </summary>
[JsonProperty]
public string parameterName { get; set; }

/// <summary>
/// For parameters that contain data structures, the path inside the data structure pointing to the field/property that
/// caused the deserialization or validation error.
/// </summary>
public string[] ErrorPath { get; set; }

/// <summary>
/// Initializes a new instance of the <see cref="InvalidParameterValueException"/> class with a specified error message and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="paramName">Name of the parameter which has an invalid value</param>
/// <param name="errorPath">For parameters that contain data structures, the path inside the data structure pointing to the field/property that
/// caused the deserialization or validation error.</param>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="encrypted">Optional. A collection of type <see cref="Tags"/> that contains additional data about the exception, which must be encrypted when stored.</param>
/// <param name="unencrypted">Optional. A collection of type <see cref="Tags"/> that contains additional data about the exception, which needn't be encrypted when stored.</param>
/// <param name="innerException">Optional. The exception that is the cause of the current exception.</param>
public InvalidParameterValueException(string paramName, string[] errorPath, string message, Exception innerException = null, Tags encrypted = null, Tags unencrypted = null) : base(message, innerException, encrypted, unencrypted)
{
parameterName = paramName;
ErrorPath = errorPath;
}

/// <summary>Initializes a new instance of the <see cref="InvalidParameterValueException"/> class with serialized data.</summary>
/// <param name="info"> The <see cref="SerializationInfo"/> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="StreamingContext"/> that contains contextual information about the source or destination.</param>
/// <exception cref="ArgumentNullException">The <paramref name="info"/> parameter is null.</exception>
/// <exception cref="SerializationException">The class name is null or <see cref="Exception.HResult"/> is zero (0). </exception>
public InvalidParameterValueException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}
1 change: 1 addition & 0 deletions Gigya.ServiceContract/Gigya.ServiceContract.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<Compile Include="Attributes\PublicEndpointAttribute.cs" />
<Compile Include="Exceptions\Breadcrumb.cs" />
<Compile Include="Exceptions\EnvironmentException.cs" />
<Compile Include="Exceptions\InvalidParameterValueException.cs" />
<Compile Include="Exceptions\ProgrammaticException.cs" />
<Compile Include="Exceptions\RemoteServiceException.cs" />
<Compile Include="Exceptions\RequestException.cs" />
Expand Down
36 changes: 33 additions & 3 deletions Gigya.ServiceContract/JsonHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
#endregion

using System;
using System.Linq;
using System.Text.RegularExpressions;
using Gigya.ServiceContract.Exceptions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

Expand All @@ -33,6 +36,9 @@ public static class JsonHelper
{
private static JsonSerializer Serializer { get; } = new JsonSerializer { DateTimeZoneHandling = DateTimeZoneHandling.RoundtripKind };

private const string ParamCaptureName = "param";
private static readonly Regex ParamRegex = new Regex(@"Path\s'(?<" + ParamCaptureName + ">.*)'.$", RegexOptions.Compiled | RegexOptions.CultureInvariant);


/// <summary>
/// Converts values that were deserialized from JSON with weak typing (e.g. into <see cref="object"/>) back into
Expand Down Expand Up @@ -61,10 +67,34 @@ public static object ConvertWeaklyTypedValue(object value, Type targetType)
return dto.LocalDateTime;
}

if (value is string && Type.GetTypeCode(paramType) == TypeCode.Object && paramType != typeof(DateTimeOffset) && paramType != typeof(TimeSpan) && paramType != typeof(Guid) && paramType != typeof(byte[]))
return JsonConvert.DeserializeObject((string)value, paramType);
try
{
if (value is string && Type.GetTypeCode(paramType) == TypeCode.Object &&
paramType != typeof(DateTimeOffset) && paramType != typeof(TimeSpan) && paramType != typeof(Guid) &&
paramType != typeof(byte[]))
return JsonConvert.DeserializeObject((string) value, paramType);

return JToken.FromObject(value).ToObject(targetType, Serializer);
}
catch (JsonReaderException jsException)
{
var parameterPath = string.IsNullOrEmpty(jsException.Path) ? new string[0] : jsException.Path.Split('.');
throw new InvalidParameterValueException(null, parameterPath, jsException.Message, innerException: jsException);
}
catch (JsonSerializationException serException)
{
string parameterPathStr = null;
var match = ParamRegex.Match(serException.Message);
if (match.Success)
parameterPathStr = match.Groups[ParamCaptureName]?.Value;

throw new InvalidParameterValueException(null, parameterPathStr?.Split('.') ?? new string[0], serException.Message, innerException: serException);
}
catch (Exception ex)
{
throw new InvalidParameterValueException(null, null, ex.Message, innerException: ex);
}

return JToken.FromObject(value).ToObject(targetType, Serializer);
}
}
}
6 changes: 3 additions & 3 deletions Gigya.ServiceContract/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
[assembly: AssemblyTrademark("")]


[assembly: AssemblyInformationalVersion("2.6.1")]// if pre-release should be in the format of "2.4.11-pre01".
[assembly: AssemblyVersion("2.6.1")]
[assembly: AssemblyFileVersion("2.6.1")]
[assembly: AssemblyInformationalVersion("2.7.1")]// if pre-release should be in the format of "2.4.11-pre01".
[assembly: AssemblyVersion("2.7.1")]
[assembly: AssemblyFileVersion("2.7.1")]



Expand Down
11 changes: 11 additions & 0 deletions Gigya.ServiceContract/ServiceContract.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{E38B20
paket.lock = paket.lock
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gigya.Microdot.ServiceContract.UnitTests", "..\tests\Gigya.Microdot.ServiceContract.UnitTests\Gigya.Microdot.ServiceContract.UnitTests.csproj", "{C224F79A-EAB5-48B8-B587-65772B0966EF}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{6D04C065-F8ED-408D-BE23-722DA84AD2F5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -21,10 +25,17 @@ Global
{DB6D3561-835E-40D5-B9D4-83951CF426DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DB6D3561-835E-40D5-B9D4-83951CF426DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DB6D3561-835E-40D5-B9D4-83951CF426DF}.Release|Any CPU.Build.0 = Release|Any CPU
{C224F79A-EAB5-48B8-B587-65772B0966EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C224F79A-EAB5-48B8-B587-65772B0966EF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C224F79A-EAB5-48B8-B587-65772B0966EF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C224F79A-EAB5-48B8-B587-65772B0966EF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{C224F79A-EAB5-48B8-B587-65772B0966EF} = {6D04C065-F8ED-408D-BE23-722DA84AD2F5}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {7F592F51-0A62-4EA9-8FA8-4544B4888416}
EndGlobalSection
Expand Down
6 changes: 3 additions & 3 deletions SolutionVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
[assembly: AssemblyCopyright("© 2018 Gigya Inc.")]
[assembly: AssemblyDescription("Microdot Framework")]

[assembly: AssemblyVersion("1.13.1.0")]
[assembly: AssemblyFileVersion("1.13.1.0")]
[assembly: AssemblyInformationalVersion("1.13.1.0")]
[assembly: AssemblyVersion("1.13.2.0")]
[assembly: AssemblyFileVersion("1.13.2.0")]
[assembly: AssemblyInformationalVersion("1.13.2.0")]

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
Expand Down
2 changes: 1 addition & 1 deletion paket.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ NUGET
DataAnnotationsValidator (2.1)
FluentAssertions (5.5.3)
System.ValueTuple (>= 4.4)
Gigya.ServiceContract (2.6)
Gigya.ServiceContract (2.7.1)
Newtonsoft.Json (>= 9.0.1)
Metrics.NET (0.5.5)
Microsoft.Bcl (1.1.10)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Gigya.ServiceContract\Gigya.ServiceContract.csproj">
<Project>{db6d3561-835e-40d5-b9d4-83951cf426df}</Project>
<Name>Gigya.ServiceContract</Name>
</ProjectReference>
<ProjectReference Include="..\..\Gigya.ServiceContract\Gigya.ServiceContract.csproj">
<Project>{db6d3561-835e-40d5-b9d4-83951cf426df}</Project>
<Name>Gigya.ServiceContract</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
Expand Down
75 changes: 71 additions & 4 deletions tests/Gigya.Microdot.ServiceContract.UnitTests/JsonHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,16 @@
using System;
using System.Linq;
using System.Numerics;
using Gigya.ServiceContract.Exceptions;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using Shouldly;

namespace Gigya.Common.Contracts.UnitTests
{
public enum MyEnum { Zero, One, Two }
public class SomeClass { public int A; public long B; public ushort? C; }
public class SomeClass { public int A; public long B; public ushort? C; [JsonProperty] SomeClass Inner; }
public struct SomeStruct { public int A; public long B; public ushort? C; }

[TestFixture]
Expand Down Expand Up @@ -232,7 +234,7 @@ public void ConvertWeaklyTypedValue_TimeSpanAsString_ShouldConvert()
[Test]
public void ConvertWeaklyTypedValue_TimeSpanAsInvalidString_ShouldConvert()
{
Should.Throw<Exception>(() => JsonHelper.ConvertWeaklyTypedValue("INVALID", typeof(TimeSpan)));
Should.Throw<InvalidParameterValueException>(() => JsonHelper.ConvertWeaklyTypedValue("INVALID", typeof(TimeSpan)));
}


Expand Down Expand Up @@ -306,7 +308,7 @@ public void ConvertWeaklyTypedValue_NumericValue(object value, Type targetType)
actual.ShouldBeOfType(Nullable.GetUnderlyingType(targetType) ?? targetType);
actual.ShouldBe(value);
}
catch (Exception ex) when (ex is OverflowException || ex.InnerException is OverflowException) { }
catch (InvalidParameterValueException) { }
}


Expand All @@ -326,7 +328,72 @@ public void ConvertWeaklyTypedValue_NullableNumericValue(object value, Type targ

actual.ShouldBe(value);
}
catch (Exception ex) when (ex is OverflowException || ex.InnerException is OverflowException) { }
catch (InvalidParameterValueException) { }
}

[Test]
public void ComplexObjectWithNullValue_ThrowException()
{
var json = JObject.Parse(@"{A:null}");
try
{
JsonHelper.ConvertWeaklyTypedValue(json, typeof(SomeClass));
Assert.Fail("Should throw exception because field 'A' is null");
}
catch (InvalidParameterValueException ex)
{
ex.parameterName.ShouldBeNull();
ex.ErrorPath.SequenceEqual(new[] {"A"});
}
}

[Test]
public void ComplexObjectWithWrongValue_ThrowException()
{
var json = JObject.Parse(@"{A:""abcd""}");
try
{
JsonHelper.ConvertWeaklyTypedValue(json, typeof(SomeClass));
Assert.Fail("Should throw exception because field 'A' has invalid value");
}
catch (InvalidParameterValueException ex)
{
ex.parameterName.ShouldBeNull();
ex.ErrorPath.SequenceEqual(new[] { "A" });
}
}

[Test]
public void ComplexObjectWithComplexObjectWithNullValue_ThrowException()
{
var json = JObject.Parse(@"{Inner: {A:null}}");
try
{
JsonHelper.ConvertWeaklyTypedValue(json, typeof(SomeClass));
Assert.Fail("Should throw exception because field 'Inner.A' is null");
}
catch (InvalidParameterValueException ex)
{
ex.parameterName.ShouldBeNull();
ex.ErrorPath.SequenceEqual(new[] { "Inner", "A" });
}
}

[Test]
public void ComplexObjectWithComplexObjectWithWrongValue_ThrowException()
{
var json = JObject.Parse(@"{Inner: {A:""abcd""}}");
try
{
JsonHelper.ConvertWeaklyTypedValue(json, typeof(SomeClass));
Assert.Fail("Should throw exception because field 'Inner.A' has invalid value");
}
catch (InvalidParameterValueException ex)
{
ex.parameterName.ShouldBeNull();
ex.ErrorPath.SequenceEqual(new[] { "Inner","A" });
}
}

}
}
Loading

0 comments on commit 5187a80

Please sign in to comment.