Skip to content

Commit c685a2d

Browse files
committed
Stabilize http response collection
1 parent 0bcfd90 commit c685a2d

12 files changed

Lines changed: 109 additions & 65 deletions

src/Dibix.Sdk.CodeGeneration/Lookup/ActionTargetDefinitionResolver.cs

Lines changed: 3 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Net;
43
using Dibix.Sdk.Abstractions;
54

65
namespace Dibix.Sdk.CodeGeneration
@@ -21,14 +20,13 @@ protected ActionTargetDefinitionResolver(ISchemaRegistry schemaRegistry, ILogger
2120
#endregion
2221

2322
#region Abstract Methods
24-
public abstract bool TryResolve<T>(string targetName, SourceLocation sourceLocation, IReadOnlyDictionary<string, ExplicitParameter> explicitParameters, IReadOnlyDictionary<string, PathParameter> readOnlyDictionary, ICollection<string> bodyParameters, ActionRequestBody requestBody, IDictionary<HttpStatusCode, ActionResponse> responses, out T actionTargetDefinition) where T : ActionTargetDefinition, new();
23+
public abstract bool TryResolve<T>(string targetName, SourceLocation sourceLocation, IReadOnlyDictionary<string, ExplicitParameter> explicitParameters, IReadOnlyDictionary<string, PathParameter> readOnlyDictionary, ICollection<string> bodyParameters, ActionRequestBody requestBody, Func<ActionTarget, T> actionTargetDefinitionFactory, out T actionTargetDefinition) where T : ActionTargetDefinition;
2524
#endregion
2625

2726
#region Protected Methods
28-
protected T CreateActionTargetDefinition<T>(ActionTarget actionTarget, IReadOnlyDictionary<string, PathParameter> pathParameters, ActionRequestBody requestBody) where T : ActionTargetDefinition, new()
27+
protected T CreateActionTargetDefinition<T>(ActionTarget actionTarget, IReadOnlyDictionary<string, PathParameter> pathParameters, ActionRequestBody requestBody, Func<ActionTarget, T> actionTargetDefinitionFactory) where T : ActionTargetDefinition
2928
{
30-
T actionTargetDefinition = new T();
31-
actionTargetDefinition.Target = actionTarget;
29+
T actionTargetDefinition = actionTargetDefinitionFactory(actionTarget);
3230
actionTargetDefinition.PathParameters.AddRange(pathParameters);
3331
if (requestBody?.Contract != null)
3432
actionTargetDefinition.Parameters.Add(new ActionParameter("body", "body", requestBody.Contract, ActionParameterLocation.Body, isRequired: true, isOutput: false, defaultValue: null, sourceLocation: requestBody.Contract.Location, source: null));
@@ -108,29 +106,6 @@ protected static bool IsUserParameter(ActionParameterSourceDefinition source, st
108106
return false;
109107
}
110108
}
111-
112-
protected void RegisterErrorResponse(IDictionary<HttpStatusCode, ActionResponse> responses, ErrorResponse errorResponse)
113-
{
114-
HttpStatusCode httpStatusCode = (HttpStatusCode)errorResponse.StatusCode;
115-
if (!responses.TryGetValue(httpStatusCode, out ActionResponse response))
116-
{
117-
SchemaDefinition problemDetailsSchema = BuiltInSchemaProvider.ProblemDetailsSchema;
118-
response = new ActionResponse(httpStatusCode, new SchemaTypeReference(key: problemDetailsSchema.FullName, isNullable: false, isEnumerable: false, problemDetailsSchema.Location));
119-
responses.Add(httpStatusCode, response);
120-
}
121-
122-
if (response.Errors.TryGetValue(errorResponse.ErrorCode, out ErrorDescription existingErrorDescription))
123-
{
124-
if (existingErrorDescription.Description != errorResponse.ErrorDescription)
125-
{
126-
Logger.LogError($"Ambiguous validation error code: {existingErrorDescription.ErrorCode}{(!String.IsNullOrEmpty(existingErrorDescription.Description) ? $" ({existingErrorDescription.Description})" : null)}", existingErrorDescription.Location);
127-
Logger.LogError($"Ambiguous validation error code: {errorResponse.ErrorCode}{(!String.IsNullOrEmpty(errorResponse.ErrorDescription) ? $" ({errorResponse.ErrorDescription})" : null)}", errorResponse.SourceLocation);
128-
}
129-
return;
130-
}
131-
132-
response.Errors.Add(errorResponse.ErrorCode, new ErrorDescription(errorResponse.ErrorCode, errorResponse.ErrorDescription, errorResponse.SourceLocation));
133-
}
134109
#endregion
135110

136111
#region Private Methods

src/Dibix.Sdk.CodeGeneration/Lookup/ActionTargetDefinitionResolverFacade.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Collections.ObjectModel;
3-
using System.Net;
44
using Dibix.Sdk.Abstractions;
55

66
namespace Dibix.Sdk.CodeGeneration
@@ -35,11 +35,11 @@ string productName
3535
};
3636
}
3737

38-
public T Resolve<T>(string targetName, SourceLocation sourceLocation, IReadOnlyDictionary<string, ExplicitParameter> explicitParameters, IReadOnlyDictionary<string, PathParameter> pathParameters, ICollection<string> bodyParameters, ActionRequestBody requestBody, IDictionary<HttpStatusCode, ActionResponse> responses) where T : ActionTargetDefinition, new()
38+
public T Resolve<T>(string targetName, SourceLocation sourceLocation, IReadOnlyDictionary<string, ExplicitParameter> explicitParameters, IReadOnlyDictionary<string, PathParameter> pathParameters, ICollection<string> bodyParameters, ActionRequestBody requestBody, Func<ActionTarget, T> actionTargetDefinitionFactory) where T : ActionTargetDefinition
3939
{
4040
foreach (ActionTargetDefinitionResolver resolver in this._resolvers)
4141
{
42-
if (resolver.TryResolve(targetName, sourceLocation, explicitParameters, pathParameters, bodyParameters, requestBody, responses, out T definition))
42+
if (resolver.TryResolve(targetName, sourceLocation, explicitParameters, pathParameters, bodyParameters, requestBody, actionTargetDefinitionFactory, out T definition))
4343
return definition;
4444
}
4545

src/Dibix.Sdk.CodeGeneration/Lookup/ExternalReflectionActionTargetDefinitionResolver.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Net;
43
using Dibix.Sdk.Abstractions;
54

65
namespace Dibix.Sdk.CodeGeneration
@@ -16,7 +15,7 @@ public ExternalReflectionActionTargetDefinitionResolver(ISchemaRegistry schemaRe
1615
this._lockEntryManager = lockEntryManager;
1716
}
1817

19-
public override bool TryResolve<T>(string targetName, SourceLocation sourceLocation, IReadOnlyDictionary<string, ExplicitParameter> explicitParameters, IReadOnlyDictionary<string, PathParameter> pathParameters, ICollection<string> bodyParameters, ActionRequestBody requestBody, IDictionary<HttpStatusCode, ActionResponse> responses, out T actionTargetDefinition)
18+
public override bool TryResolve<T>(string targetName, SourceLocation sourceLocation, IReadOnlyDictionary<string, ExplicitParameter> explicitParameters, IReadOnlyDictionary<string, PathParameter> pathParameters, ICollection<string> bodyParameters, ActionRequestBody requestBody, Func<ActionTarget, T> actionTargetDefinitionFactory, out T actionTargetDefinition)
2019
{
2120
string[] parts = targetName.Split(',');
2221
if (parts.Length != 2)
@@ -58,7 +57,7 @@ public override bool TryResolve<T>(string targetName, SourceLocation sourceLocat
5857
*/
5958

6059
ActionTarget actionTarget = new ReflectionActionTarget(assemblyName, accessorFullName: typeName, methodName, isAsync: false, hasRefParameters: false, sourceLocation);
61-
actionTargetDefinition = CreateActionTargetDefinition<T>(actionTarget, pathParameters, requestBody);
60+
actionTargetDefinition = CreateActionTargetDefinition(actionTarget, pathParameters, requestBody, actionTargetDefinitionFactory);
6261
ActionParameterRegistry parameterRegistry = new ActionParameterRegistry(actionTargetDefinition, pathParameters);
6362
foreach (ExplicitParameter parameter in explicitParameters.Values)
6463
{
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
using System.Collections.Generic;
2-
using System.Net;
1+
using System;
2+
using System.Collections.Generic;
33

44
namespace Dibix.Sdk.CodeGeneration
55
{
66
internal interface IActionTargetDefinitionResolverFacade
77
{
8-
T Resolve<T>(string targetName, SourceLocation sourceLocation, IReadOnlyDictionary<string, ExplicitParameter> explicitParameters, IReadOnlyDictionary<string, PathParameter> pathParameters, ICollection<string> bodyParameters, ActionRequestBody requestBody, IDictionary<HttpStatusCode,ActionResponse> responses) where T : ActionTargetDefinition, new();
8+
T Resolve<T>(string targetName, SourceLocation sourceLocation, IReadOnlyDictionary<string, ExplicitParameter> explicitParameters, IReadOnlyDictionary<string, PathParameter> pathParameters, ICollection<string> bodyParameters, ActionRequestBody requestBody, Func<ActionTarget, T> actionTargetDefinitionFactory) where T : ActionTargetDefinition;
99
}
1010
}

src/Dibix.Sdk.CodeGeneration/Lookup/SqlStatementDefinitionActionTargetDefinitionResolver.cs

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using System.Net;
45
using Dibix.Sdk.Abstractions;
@@ -33,7 +34,7 @@ string productName
3334
#endregion
3435

3536
#region Overrides
36-
public override bool TryResolve<T>(string targetName, SourceLocation sourceLocation, IReadOnlyDictionary<string, ExplicitParameter> explicitParameters, IReadOnlyDictionary<string, PathParameter> pathParameters, ICollection<string> bodyParameters, ActionRequestBody requestBody, IDictionary<HttpStatusCode, ActionResponse> responses, out T actionTargetDefinition)
37+
public override bool TryResolve<T>(string targetName, SourceLocation sourceLocation, IReadOnlyDictionary<string, ExplicitParameter> explicitParameters, IReadOnlyDictionary<string, PathParameter> pathParameters, ICollection<string> bodyParameters, ActionRequestBody requestBody, Func<ActionTarget, T> actionTargetDefinitionFactory, out T actionTargetDefinition)
3738
{
3839
if (!TryGetStatementDefinitionByProbing(targetName, out SqlStatementDefinition statementDefinition))
3940
{
@@ -48,7 +49,7 @@ public override bool TryResolve<T>(string targetName, SourceLocation sourceLocat
4849
bool isAsync = statementDefinition.Async;
4950
bool hasRefParameters = statementDefinition.Parameters.Any(x => x.IsOutput);
5051
ActionTarget actionTarget = new LocalActionTarget(statementDefinition, localAccessorFullName, externalAccessorFullName, definitionName, isAsync, hasRefParameters, sourceLocation);
51-
actionTargetDefinition = CreateActionTargetDefinition<T>(actionTarget, pathParameters, requestBody);
52+
actionTargetDefinition = CreateActionTargetDefinition(actionTarget, pathParameters, requestBody, actionTargetDefinitionFactory);
5253
ActionParameterRegistry parameterRegistry = new ActionParameterRegistry(actionTargetDefinition, pathParameters);
5354
foreach (SqlQueryParameter parameter in statementDefinition.Parameters)
5455
{
@@ -75,12 +76,20 @@ public override bool TryResolve<T>(string targetName, SourceLocation sourceLocat
7576
explicitParameter.Visited = true;
7677
}
7778

78-
foreach (ErrorResponse errorResponse in statementDefinition.ErrorResponses)
79-
RegisterErrorResponse(responses, errorResponse);
79+
if (statementDefinition.Results.Any(x => x.ResultMode == SqlQueryResultMode.Single) && actionTargetDefinition.Responses.ContainsKey(HttpStatusCode.NotFound))
80+
{
81+
// Automatic status code detection
82+
actionTargetDefinition.Responses.Add(HttpStatusCode.NotFound, new ActionResponse(HttpStatusCode.NotFound));
83+
}
8084

8185
if (actionTargetDefinition is ActionDefinition actionDefinition)
8286
CollectResponse(actionDefinition, statementDefinition);
8387

88+
foreach (ErrorResponse errorResponse in statementDefinition.ErrorResponses)
89+
{
90+
actionTargetDefinition.RegisterErrorResponse(errorResponse.StatusCode, errorResponse.ErrorCode, errorResponse.ErrorDescription, errorResponse.SourceLocation, Logger);
91+
}
92+
8493
return true;
8594
}
8695
#endregion

src/Dibix.Sdk.CodeGeneration/Model/ActionDefinition.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,17 @@ public TypeReference DefaultResponseType
2222
get => GetDefaultResponseType();
2323
set => SetDefaultResponseType(value);
2424
}
25-
public IDictionary<HttpStatusCode, ActionResponse> Responses { get; } = new Dictionary<HttpStatusCode, ActionResponse>();
25+
26+
public ActionDefinition(ActionTarget actionTarget) : base(actionTarget) { }
2627

2728
public void SetFileResponse(ActionFileResponse actionFileResponse, SourceLocation location)
2829
{
2930
FileResponse = actionFileResponse;
3031
Responses[HttpStatusCode.OK] = new ActionResponse(HttpStatusCode.OK, actionFileResponse.MediaType, resultType: ActionDefinitionUtility.CreateStreamTypeReference(location));
31-
Responses[HttpStatusCode.NotFound] = new ActionResponse(HttpStatusCode.NotFound);
32+
33+
// A custom error response might have already been registered
34+
if (!Responses.ContainsKey(HttpStatusCode.NotFound))
35+
Responses[HttpStatusCode.NotFound] = new ActionResponse(HttpStatusCode.NotFound);
3236
}
3337

3438
private TypeReference GetDefaultResponseType() => Responses.TryGetValue(HttpStatusCode.OK, out ActionResponse response) ? response.ResultType : null;

src/Dibix.Sdk.CodeGeneration/Model/ActionResponse.cs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
24
using System.Net;
5+
using Dibix.Sdk.Abstractions;
36

47
namespace Dibix.Sdk.CodeGeneration
58
{
69
public sealed class ActionResponse
710
{
11+
private readonly IDictionary<int, ErrorDescription> _errors = new Dictionary<int, ErrorDescription>();
12+
813
public HttpStatusCode StatusCode { get; }
914
public string MediaType { get; } = HttpMediaType.Json;
1015
public TypeReference ResultType { get; set; }
1116
public string Description { get; set; }
1217
public ErrorDescription StatusCodeDetectionDetail { get; set; }
13-
public IDictionary<int, ErrorDescription> Errors { get; } = new Dictionary<int, ErrorDescription>();
18+
public ICollection<ErrorDescription> Errors => _errors.Values;
1419

1520
public ActionResponse(HttpStatusCode statusCode)
1621
{
@@ -25,5 +30,25 @@ public ActionResponse(HttpStatusCode statusCode, string mediaType, TypeReference
2530
MediaType = mediaType;
2631
ResultType = resultType;
2732
}
33+
34+
public void AddError(int errorCode, string errorDescription, SourceLocation sourceLocation, ILogger logger)
35+
{
36+
if (!_errors.Any())
37+
{
38+
SchemaDefinition problemDetailsSchema = BuiltInSchemaProvider.ProblemDetailsSchema;
39+
ResultType = new SchemaTypeReference(key: problemDetailsSchema.FullName, isNullable: false, isEnumerable: false, problemDetailsSchema.Location);
40+
}
41+
42+
if (_errors.TryGetValue(errorCode, out ErrorDescription existingErrorDescription))
43+
{
44+
if (existingErrorDescription.Description != errorDescription)
45+
{
46+
logger.LogError($"Ambiguous validation error code: {existingErrorDescription.ErrorCode}{(!String.IsNullOrEmpty(existingErrorDescription.Description) ? $" ({existingErrorDescription.Description})" : null)}", existingErrorDescription.Location);
47+
logger.LogError($"Ambiguous validation error code: {errorCode}{(!String.IsNullOrEmpty(errorDescription) ? $" ({errorDescription})" : null)}", sourceLocation);
48+
}
49+
return;
50+
}
51+
_errors.Add(errorCode, new ErrorDescription(errorCode, errorDescription, sourceLocation));
52+
}
2853
}
2954
}
Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
11
using System.Collections.Generic;
22
using System.Collections.ObjectModel;
33
using System.Net;
4+
using Dibix.Sdk.Abstractions;
45

56
namespace Dibix.Sdk.CodeGeneration
67
{
78
public abstract class ActionTargetDefinition
89
{
9-
public ActionTarget Target { get; set; }
10+
public ActionTarget Target { get; }
1011
public IList<ActionParameter> Parameters { get; } = new Collection<ActionParameter>();
1112
public IDictionary<string, PathParameter> PathParameters { get; } = new Dictionary<string, PathParameter>();
13+
public IDictionary<HttpStatusCode, ActionResponse> Responses { get; } = new Dictionary<HttpStatusCode, ActionResponse>();
14+
15+
protected ActionTargetDefinition(ActionTarget target)
16+
{
17+
Target = target;
18+
}
19+
20+
public virtual void RegisterErrorResponse(int statusCode, int errorCode, string errorDescription, SourceLocation sourceLocation, ILogger logger)
21+
{
22+
HttpStatusCode httpStatusCode = (HttpStatusCode)statusCode;
23+
if (!Responses.TryGetValue(httpStatusCode, out ActionResponse response))
24+
{
25+
response = new ActionResponse(httpStatusCode);
26+
Responses.Add(httpStatusCode, response);
27+
}
28+
response.AddError(errorCode, errorDescription, sourceLocation, logger);
29+
}
1230
}
1331
}
Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
1-
namespace Dibix.Sdk.CodeGeneration
1+
using Dibix.Sdk.Abstractions;
2+
3+
namespace Dibix.Sdk.CodeGeneration
24
{
35
public sealed class AuthorizationBehavior : ActionTargetDefinition
46
{
7+
public ActionDefinition Parent { get; }
8+
9+
public AuthorizationBehavior(ActionDefinition parent, ActionTarget actionTarget) : base(actionTarget)
10+
{
11+
Parent = parent;
12+
}
13+
14+
public override void RegisterErrorResponse(int statusCode, int errorCode, string errorDescription, SourceLocation sourceLocation, ILogger logger)
15+
{
16+
base.RegisterErrorResponse(statusCode, errorCode, errorDescription, sourceLocation, logger);
17+
Parent.RegisterErrorResponse(statusCode, errorCode, errorDescription, sourceLocation, logger);
18+
}
519
}
620
}

src/Dibix.Sdk.CodeGeneration/OpenApi/OpenApiGenerator.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,11 +219,10 @@ private static void AppendResponses(OpenApiDocument document, OpenApiOperation o
219219
if (sb.Length > 0)
220220
sb.AppendLine();
221221

222-
ICollection<ErrorDescription> errorDescriptions = actionResponse.Errors.Values;
223222
sb.Append($"""
224223
Code|Description
225224
-|-
226-
{String.Join(Environment.NewLine, errorDescriptions.Select(x => $"{x.ErrorCode}|{x.Description}"))}
225+
{String.Join(Environment.NewLine, actionResponse.Errors.Select(x => $"{x.ErrorCode}|{x.Description}"))}
227226
""");
228227

229228
apiResponse.Headers.Add(KnownHeaders.ClientErrorCodeHeaderName, new OpenApiHeader

0 commit comments

Comments
 (0)