/// <summary> /// Reads the JSON representation of the object. /// </summary> public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { if (reader.TokenType == JsonToken.Null) { return(null); } MatchTokenOrThrow(reader, JsonToken.StartObject); ReadOrThrow(reader); Type valueType = objectType.IsConstructedGenericType ? objectType.GenericTypeArguments[0] : null; object value = null; ServiceErrorDto error = null; while (reader.TokenType == JsonToken.PropertyName) { string propertyName = (string)reader.Value; ReadOrThrow(reader); if (string.Equals(propertyName, c_valuePropertyName, StringComparison.OrdinalIgnoreCase)) { if (valueType == null) { throw new JsonSerializationException("ServiceResult does not support 'value'; use ServiceResult<T>."); } value = serializer.Deserialize(reader, valueType); } else if (string.Equals(propertyName, c_errorPropertyName, StringComparison.OrdinalIgnoreCase)) { error = serializer.Deserialize <ServiceErrorDto>(reader); } ReadOrThrow(reader); } MatchTokenOrThrow(reader, JsonToken.EndObject); if (value != null && error != null) { throw new JsonSerializationException("ServiceResult must not have both 'value' and 'error'."); } if (valueType == null) { return(error != null?Failure(error) : Success()); } else if (error != null) { return((ServiceResult)s_genericCastMethod.MakeGenericMethod(valueType).Invoke(Failure(error), new object[0])); } else { if (value == null && valueType.GetTypeInfo().IsValueType) { value = Activator.CreateInstance(valueType); } return((ServiceResult)s_genericSuccessMethod.MakeGenericMethod(valueType).Invoke(null, new[] { value })); } }
public void MapFailure() { var error = new ServiceErrorDto("Error"); ServiceResult <int> failedValue = ServiceResult.Failure(error); failedValue.Map(x => x.ToString()).Error.Should().BeDto(error); }
/// <summary> /// Asserts that the subject result is a failure whose error is equivalent to the expected error. /// </summary> public AndConstraint <TAssertions> BeFailure(ServiceErrorDto expectedError) { BeFailure(); Execute.Assertion .ForCondition(Subject !.Error?.IsEquivalentTo(expectedError) == true) .FailWith("Expected {context:service result} to be failure with error\n {0}\n but found error\n {1}", expectedError, Subject !.Error); return(new AndConstraint <TAssertions>((TAssertions)this)); }
private static IActionResult CreateActionResultFromError(ServiceErrorDto error) { return(new ContentResult { Content = ServiceJsonUtility.ToJson(error), ContentType = HttpServiceUtility.JsonMediaType, StatusCode = (int)(HttpServiceErrors.TryGetHttpStatusCode(error.Code) ?? HttpStatusCode.InternalServerError), }); }
public void FailureAsFailure() { var error = new ServiceErrorDto("Error"); ServiceResultFailure failure = ServiceResult.Failure(error); failure.AsFailure().Error.Should().BeDto(error); ServiceResult failedResult = ServiceResult.Failure(error); failedResult.AsFailure().Error.Should().BeDto(error); ServiceResult <int> failedValue = ServiceResult.Failure(error); failedValue.AsFailure().Error.Should().BeDto(error); }
public void ArraySerialization() { var invalidRequest = new ServiceErrorDto { Code = ServiceErrors.InvalidRequest }; var invalidResponse = new ServiceErrorDto { Code = ServiceErrors.InvalidResponse }; var dto = ValueDto.Create(new List <ServiceErrorDto> { invalidRequest, invalidResponse, }); var json = "{\"errorArrayValue\":[{\"code\":\"InvalidRequest\"},{\"code\":\"InvalidResponse\"}]}"; JsonSerializer.ToJson(dto).Should().Be(json); JsonSerializer.FromJson <ValueDto>(json).Should().BeDto(dto); }
public void DictionarySerialization() { var invalidRequest = new ServiceErrorDto { Code = ServiceErrors.InvalidRequest }; var invalidResponse = new ServiceErrorDto { Code = ServiceErrors.InvalidResponse }; var dto = ValueDto.Create(new Dictionary <string, ServiceErrorDto> { ["request"] = invalidRequest, ["response"] = invalidResponse, }); var json = "{\"errorMapValue\":{\"request\":{\"code\":\"InvalidRequest\"},\"response\":{\"code\":\"InvalidResponse\"}}}"; JsonSerializer.ToJson(dto).Should().Be(json); JsonSerializer.FromJson <ValueDto>(json).Should().BeDto(dto); }
public void DictionaryClone() { var invalidRequest = new ServiceErrorDto { Code = ServiceErrors.InvalidRequest }; var invalidResponse = new ServiceErrorDto { Code = ServiceErrors.InvalidResponse }; var dto = ValueDto.Create(new Dictionary <string, ServiceErrorDto> { ["request"] = invalidRequest, ["response"] = invalidResponse, }); var clone = ServiceDataUtility.Clone(dto); clone.Should().NotBeSameAs(dto); clone.ErrorMapValue.Should().NotBeSameAs(dto.ErrorMapValue); clone.IsEquivalentTo(dto).Should().Be(true); }
public void BasicEquivalence() { var empty = new ServiceErrorDto(); var full = s_error; empty.IsEquivalentTo(null).Should().BeFalse(); empty.IsEquivalentTo(empty).Should().BeTrue(); empty.IsEquivalentTo(new ServiceErrorDto()).Should().BeTrue(); empty.IsEquivalentTo(full).Should().BeFalse(); full.IsEquivalentTo(new ServiceErrorDto(s_error.Code)).Should().BeFalse(); full.IsEquivalentTo(null).Should().BeFalse(); full.IsEquivalentTo(empty).Should().BeFalse(); full.IsEquivalentTo(new ServiceErrorDto(s_error.Code, s_error.Message) { DetailsObject = s_error.DetailsObject, InnerError = s_error.InnerError }).Should().BeTrue(); full.IsEquivalentTo(full).Should().BeTrue(); full.IsEquivalentTo(new ServiceErrorDto(s_error.Code)).Should().BeFalse(); }
private static string GetErrorString(ServiceErrorDto error, string indent = "") { var text = error.Message ?? ""; if (error.Code != null) { text += " (" + error.Code + ")"; } if (error.DetailsObject != null) { text += Environment.NewLine + indent + " Details: " + error.DetailsObject; } if (error.InnerError != null) { text += Environment.NewLine + indent + " InnerError: " + GetErrorString(error.InnerError, indent + " "); } return(text); }
public void ArraySerialization() { var invalidRequest = new ServiceErrorDto { Code = ServiceErrors.InvalidRequest }; var invalidResponse = new ServiceErrorDto { Code = ServiceErrors.InvalidResponse }; var dto = ValueDto.Create(new List <ServiceErrorDto> { invalidRequest, invalidResponse, }); string json = "{\"errorArrayValue\":[{\"code\":\"InvalidRequest\"},{\"code\":\"InvalidResponse\"}]}"; ServiceJsonUtility.ToJson(dto).Should().Be(json); ServiceJsonUtility.FromJson <ValueDto>(json).Should().BeDto(dto); var token = ServiceJsonUtility.FromJson <JToken>(json); token["errorArrayValue"].Type.Should().Be(JTokenType.Array); ServiceJsonUtility.ToJson(token).Should().Be(json); }
public void DictionarySerialization() { var invalidRequest = new ServiceErrorDto { Code = ServiceErrors.InvalidRequest }; var invalidResponse = new ServiceErrorDto { Code = ServiceErrors.InvalidResponse }; var dto = ValueDto.Create(new Dictionary <string, ServiceErrorDto> { ["request"] = invalidRequest, ["response"] = invalidResponse, }); string json = "{\"errorMapValue\":{\"request\":{\"code\":\"InvalidRequest\"},\"response\":{\"code\":\"InvalidResponse\"}}}"; ServiceJsonUtility.ToJson(dto).Should().Be(json); ServiceJsonUtility.FromJson <ValueDto>(json).Should().BeDto(dto); var token = ServiceJsonUtility.FromJson <JToken>(json); token["errorMapValue"].Type.Should().Be(JTokenType.Object); ServiceJsonUtility.ToJson(token).Should().Be(json); }
/// <summary> /// Attempts to handle a service method. /// </summary> protected async Task <HttpResponseMessage> TryHandleServiceMethodAsync <TRequest, TResponse>(HttpMethodMapping <TRequest, TResponse> mapping, HttpRequestMessage httpRequest, Func <TRequest, CancellationToken, Task <ServiceResult <TResponse> > > invokeMethodAsync, CancellationToken cancellationToken) where TRequest : ServiceDto, new() where TResponse : ServiceDto, new() { if (httpRequest.Method != mapping.HttpMethod) { return(null); } var pathParameters = TryMatchHttpRoute(httpRequest.RequestUri, m_rootPath + mapping.Path); if (pathParameters == null) { return(null); } var context = new ServiceHttpContext(); ServiceHttpContext.SetContext(httpRequest, context); var aspectHttpResponse = await AdaptTask(RequestReceivedAsync(httpRequest, cancellationToken)).ConfigureAwait(true); if (aspectHttpResponse != null) { return(aspectHttpResponse); } string mediaType = GetResponseMediaType(httpRequest); ServiceErrorDto error = null; object requestBody = null; if (mapping.RequestBodyType != null) { var requestResult = await AdaptTask(m_contentSerializer.ReadHttpContentAsync(mapping.RequestBodyType, httpRequest.Content, cancellationToken)).ConfigureAwait(true); if (requestResult.IsFailure) { error = requestResult.Error; } else { requestBody = requestResult.Value; } } TResponse response = null; if (error == null) { var request = mapping.CreateRequest(requestBody); var uriParameters = new Dictionary <string, string>(); foreach (var queryParameter in ParseQueryString(httpRequest.RequestUri.Query)) { uriParameters[queryParameter.Key] = queryParameter.Value[0]; } foreach (var pathParameter in pathParameters) { uriParameters[pathParameter.Key] = pathParameter.Value; } request = mapping.SetUriParameters(request, uriParameters); request = mapping.SetRequestHeaders(request, HttpServiceUtility.CreateDictionaryFromHeaders(httpRequest.Headers)); context.Request = request; var methodResult = await invokeMethodAsync(request, cancellationToken).ConfigureAwait(true); if (methodResult.IsFailure) { error = methodResult.Error; } else { response = methodResult.Value; } context.Result = error != null?ServiceResult.Failure(error) : ServiceResult.Success <ServiceDto>(response); } HttpResponseMessage httpResponse; if (error == null) { var responseMappingGroups = mapping.ResponseMappings .GroupBy(x => x.MatchesResponse(response)) .Where(x => x.Key != false) .OrderByDescending(x => x.Key) .ToList(); if (responseMappingGroups.Count >= 1 && responseMappingGroups[0].Count() == 1) { var responseMapping = responseMappingGroups[0].Single(); httpResponse = new HttpResponseMessage(responseMapping.StatusCode); var headersResult = HttpServiceUtility.TryAddHeaders(httpResponse.Headers, mapping.GetResponseHeaders(response)); if (headersResult.IsFailure) { throw new InvalidOperationException(headersResult.Error.Message); } if (responseMapping.ResponseBodyType != null) { httpResponse.Content = m_contentSerializer.CreateHttpContent(responseMapping.GetResponseBody(response), mediaType); } } else { throw new InvalidOperationException($"Found {responseMappingGroups.Sum(x => x.Count())} valid HTTP responses for {typeof(TResponse).Name}: {response}"); } } else { var statusCode = error.Code == null ? HttpStatusCode.InternalServerError : (TryGetCustomHttpStatusCode(error.Code) ?? HttpServiceErrors.TryGetHttpStatusCode(error.Code) ?? HttpStatusCode.InternalServerError); httpResponse = new HttpResponseMessage(statusCode); if (statusCode != HttpStatusCode.NoContent && statusCode != HttpStatusCode.NotModified) { httpResponse.Content = m_contentSerializer.CreateHttpContent(error, mediaType); } } httpResponse.RequestMessage = httpRequest; await AdaptTask(ResponseReadyAsync(httpResponse, cancellationToken)).ConfigureAwait(true); return(httpResponse); }
/// <summary> /// Creates an exception from an error data object and an inner exception. /// </summary> public ServiceException(ServiceErrorDto error, Exception?innerException) : base(null, innerException) { Error = error ?? throw new ArgumentNullException(nameof(error)); }
private ServiceResult(ServiceErrorDto error) : base(error) { }
/// <summary> /// Runs the test with the specified name. /// </summary> public async Task <ConformanceTestResult> RunTestAsync(string testName, CancellationToken cancellationToken) { try { ConformanceTestResult failure(string message) => new ConformanceTestResult(testName, ConformanceTestStatus.Fail, message); var testInfo = m_testProvider.TryGetTestInfo(testName); var api = m_getApiForTest(testName); string capitalize(string value) => value.Substring(0, 1).ToUpperInvariant() + value.Substring(1); var methodInfo = typeof(IConformanceApi).GetMethod(capitalize(testInfo.Method) + "Async", BindingFlags.Public | BindingFlags.Instance); if (methodInfo == null) { return(failure($"Missing API method for {testInfo.Method}")); } var requestJObject = testInfo.Request; var requestDto = ServiceJsonUtility.FromJToken(requestJObject, methodInfo.GetParameters()[0].ParameterType); var requestRoundTripJObject = ServiceJsonUtility.ToJToken(requestDto); if (!JToken.DeepEquals(requestJObject, requestRoundTripJObject)) { return(failure($"Request round trip failed. expected={ServiceJsonUtility.ToJson(requestJObject)} actual={ServiceJsonUtility.ToJson(requestRoundTripJObject)}")); } var task = (Task)methodInfo.Invoke(api, new[] { requestDto, cancellationToken }); await task.ConfigureAwait(false); dynamic result = ((dynamic)task).Result; ServiceDto actualResponseDto = (ServiceDto)result.GetValueOrDefault(); var expectedResponseJObject = testInfo.Response; var expectedErrorJObject = testInfo.Error; if (actualResponseDto != null) { var actualResponseJObject = (JObject)ServiceJsonUtility.ToJToken(actualResponseDto); if (expectedErrorJObject != null) { return(failure($"Got valid response; expected error. expected={ServiceJsonUtility.ToJson(expectedErrorJObject)} actual={ServiceJsonUtility.ToJson(actualResponseJObject)}")); } if (!JToken.DeepEquals(expectedResponseJObject, actualResponseJObject)) { return(failure($"Response JSON did not match. expected={ServiceJsonUtility.ToJson(expectedResponseJObject)} actual={ServiceJsonUtility.ToJson(actualResponseJObject)}")); } var responseType = methodInfo.ReturnType.GetGenericArguments()[0].GetGenericArguments()[0]; var expectedResponseDto = (ServiceDto)ServiceJsonUtility.FromJToken(expectedResponseJObject, responseType); if (!expectedResponseDto.IsEquivalentTo(actualResponseDto)) { return(failure($"Response DTO did not match. expected={expectedResponseDto} actual={ServiceJsonUtility.ToJson(actualResponseDto)}")); } } else { ServiceErrorDto actualErrorDto = result.Error; var actualErrorJObject = (JObject)ServiceJsonUtility.ToJToken(actualErrorDto); if (expectedErrorJObject == null) { return(failure($"Got error; expected valid response. expected={ServiceJsonUtility.ToJson(expectedResponseJObject)} actual={ServiceJsonUtility.ToJson(actualErrorJObject)}")); } if (!JToken.DeepEquals(expectedErrorJObject, actualErrorJObject)) { return(failure($"Error JSON did not match. expected={ServiceJsonUtility.ToJson(expectedErrorJObject)} actual={ServiceJsonUtility.ToJson(actualErrorJObject)}")); } var expectedErrorDto = ServiceJsonUtility.FromJToken <ServiceErrorDto>(expectedErrorJObject); if (!expectedErrorDto.IsEquivalentTo(actualErrorDto)) { return(failure($"Error DTO did not match. expected={expectedErrorDto} actual={actualErrorDto}")); } } return(new ConformanceTestResult(testName, ConformanceTestStatus.Pass)); } catch (Exception exception) { return(new ConformanceTestResult(testName, ConformanceTestStatus.Fail, $"Unhandled exception {exception.GetType().FullName}: {exception.Message}")); } }
internal ServiceResultFailure(ServiceErrorDto error) : base(error) { }
/// <summary> /// Creates an exception from an error data object. /// </summary> public ServiceException(ServiceErrorDto error) { Error = error ?? throw new ArgumentNullException(nameof(error)); }