• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

AndreuCodina / CrossValidation / 5635033475

23 Jul 2023 06:48AM UTC coverage: 95.576% (-0.2%) from 95.791%
5635033475

push

github

AndreuCodina
Update CodeUrl

413 of 458 branches covered (90.17%)

Branch coverage included in aggregate %.

28 of 28 new or added lines in 3 files covered. (100.0%)

1380 of 1418 relevant lines covered (97.32%)

177.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.01
/src/CrossValidation/DependencyInjection/CrossValidationMiddleware.cs
1
using System.Net;
2
using System.Text.Json;
3
using CrossValidation.Exceptions;
4
using CrossValidation.Utils;
5
using Microsoft.AspNetCore.Hosting;
6
using Microsoft.AspNetCore.Http;
7
using Microsoft.Extensions.Logging;
8

9
namespace CrossValidation.DependencyInjection;
10

11
public class CrossValidationMiddleware : IMiddleware
12
{
13
    private readonly ILogger<CrossValidationMiddleware> _logger;
14
    private readonly IHostingEnvironment _environment;
15

16
    public CrossValidationMiddleware(ILoggerFactory loggerFactory, IHostingEnvironment environment)
26✔
17
    {
18
        _logger = loggerFactory.CreateLogger<CrossValidationMiddleware>();
26✔
19
        _environment = environment;
26✔
20
    }
26✔
21
    
22
    public async Task InvokeAsync(HttpContext context, RequestDelegate next)
23
    {
24
        try
25
        {
26
            await next(context);
26✔
27
        }
×
28
        catch (Exception e)
26✔
29
        {
30
            await HandleException(e, context);
26✔
31
        }
32
    }
26✔
33

34
    private async Task HandleException(Exception exception, HttpContext context)
35
    {
36
        var problemDetails = new CrossProblemDetails();
26✔
37
        var statusCode = HttpStatusCode.InternalServerError;
26✔
38
        string? type = null;
26✔
39
        string? title = null;
26✔
40
        string? detail = null;
26✔
41
        List<CrossProblemDetailsError> errors = new();
26✔
42
        string? exceptionDetail = null;
26✔
43
        
44
        if (exception is BusinessException businessException)
26✔
45
        {
46
            statusCode = businessException.StatusCode;
24✔
47
            title = "A validation error occurred";
24✔
48
            var error = CreateCrossProblemDetailsError(businessException, context);
24✔
49
            
50
            var allErrorCustomizationsAreNotSet =
24✔
51
                error is
24✔
52
                {
24✔
53
                    Code: null,
24✔
54
                    Message: null,
24✔
55
                    Detail: null,
24✔
56
                    Placeholders: null
24✔
57
                };
24✔
58

59
            if (!allErrorCustomizationsAreNotSet)
24✔
60
            {
61
                errors.Add(error);
21✔
62
            }
63
        }
64
        else if (exception is BusinessListException validationListException)
2✔
65
        {
66
            statusCode = HttpStatusCode.UnprocessableEntity;
1✔
67
            title = "Several validation errors occurred";
1✔
68

69
            foreach (var error in validationListException.Exceptions)
2!
70
            {
71
                errors.Add(CreateCrossProblemDetailsError(error, context));
×
72
            }
73
        }
74
        else if (exception is NonNullablePropertyIsNullException nonNullablePropertyIsNullException)
1!
75
        {
76
            statusCode = HttpStatusCode.BadRequest;
×
77
            title = "Nullability error";
×
78
            detail = $"Non nullable property is null: {nonNullablePropertyIsNullException.PropertyName}";
×
79
        }
80
        else if (exception is NonNullableItemCollectionWithNullItemException
1!
81
                 nonNullableItemCollectionWithNullItemException)
1✔
82
        {
83
            statusCode = HttpStatusCode.BadRequest;
×
84
            title = "Nullability error";
×
85
            detail = $"Non nullable item collection with null item: {nonNullableItemCollectionWithNullItemException.CollectionName}";
×
86
        }
87
        else
88
        {
89
            if (!CrossValidationOptions.HandleUnknownException)
1!
90
            {
91
                return;
×
92
            }
93
            
94
            _logger.LogError(exception, exception.Message);
1✔
95
            type = "https://datatracker.ietf.org/doc/html/rfc9110#section-15.6.1";
1✔
96
            title = "An error occurred";
1✔
97
            detail = _environment.IsDevelopment() ? exception.Message : null;
1!
98
            exceptionDetail = _environment.IsDevelopment() ? exception.ToString() : null;
1!
99
        }
100

101
        problemDetails.Status = (int)statusCode;
26✔
102
        problemDetails.Type = type;
26✔
103
        problemDetails.Title = title;
26✔
104
        problemDetails.Detail = detail;
26✔
105
        problemDetails.Errors = errors.Any() ? errors : null;
26✔
106
        problemDetails.ExceptionDetail = exceptionDetail;
26✔
107
        var response = JsonSerializer.Serialize(problemDetails);
26✔
108
        context.Response.StatusCode = problemDetails.Status.Value;
26✔
109
        context.Response.ContentType = "application/problem+json";
26✔
110
        context.Response.Headers.Add("X-Trace-Id", context.Request.Headers["X-Trace-Id"]);
26✔
111
        await context.Response.WriteAsync(response);
26✔
112
    }
26✔
113

114
    private CrossProblemDetailsError CreateCrossProblemDetailsError(BusinessException exception, HttpContext context)
115
    {
116
        var error = new CrossProblemDetailsError
24✔
117
        {
24✔
118
            Code = exception.Code,
24✔
119
            CodeUrl = GetPublicationUrl(exception, context),
24✔
120
            Message = exception.Message == "" ? null : exception.Message,
24✔
121
            Detail = exception.Details,
24✔
122
            Placeholders = GetPlaceholders(exception)
24✔
123
        };
24✔
124

125
        return error;
24✔
126
    }
127

128
    private string? GetPublicationUrl(BusinessException exception, HttpContext context)
129
    {
130
        string? baseUrl = null;
24✔
131
        
132
        if (exception.Code is null)
24✔
133
        {
134
            return null;
4✔
135
        }
136

137
        if (CrossValidationOptions.PublicationUrl is not null)
20✔
138
        {
139
            baseUrl = CrossValidationOptions.PublicationUrl;
1✔
140
        }
141
        else if (_environment.IsDevelopment())
19!
142
        {
143
            var protocol = context.Request.IsHttps ? "https" : "http";
19!
144
            var host = context.Request.Host.Value;
19✔
145
            var port = context.Request.Host.Port is not null
19!
146
                ? $":{context.Request.Host.Port.Value}"
19✔
147
                : null;
19✔
148
            baseUrl = $"{protocol}://{host}{port}";
19✔
149
        }
150
        else
151
        {
152
            return null;
×
153
        }
154
        
155
        return $"{baseUrl}/error-codes#{exception.Code}";
20✔
156
    }
157

158
    private Dictionary<string, object?>? GetPlaceholders(BusinessException exception)
159
    {
160
        var hasToSendPlaceholders =
24!
161
            CrossValidationOptions.LocalizeCommonErrorsInFront
24✔
162
            && exception.PlaceholderValues.Count > 0;
24✔
163

164
        if (exception is not FrontBusinessException && !hasToSendPlaceholders)
24✔
165
        {
166
            return null;
23✔
167
        }
168
        
169
        var placeholders = new Dictionary<string, object?>();
1✔
170

171
        foreach (var placeholder in exception.PlaceholderValues)
6✔
172
        {
173
            placeholders.Add(placeholder.Key, placeholder.Value);
2✔
174
        }
175

176
        return placeholders;
1✔
177
    }
178
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc