Skip to content

Commit 7b0f3cd

Browse files
authored
Merge pull request #167 from microsoft/feature/error-schema
adds the ability to define error responses on error codes instead of default
2 parents dbcf686 + b1dba80 commit 7b0f3cd

File tree

50 files changed

+427
-140
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+427
-140
lines changed

src/Microsoft.OpenApi.OData.Reader/Common/Constants.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,16 @@ internal static class Constants
4545
/// </summary>
4646
public static string StatusCodeDefault = "default";
4747

48+
/// <summary>
49+
/// Status code class: 4XX
50+
/// </summary>
51+
public static string StatusCodeClass4XX = "4XX";
52+
53+
/// <summary>
54+
/// Status code class: 5XX
55+
/// </summary>
56+
public static string StatusCodeClass5XX = "5XX";
57+
4858
/// <summary>
4959
/// Edm model error extension key.
5060
/// </summary>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// ------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
4+
// ------------------------------------------------------------
5+
6+
using Microsoft.OpenApi.Models;
7+
using Microsoft.OpenApi.OData.Generator;
8+
9+
namespace Microsoft.OpenApi.OData.Common;
10+
11+
/// <summary>
12+
/// Extensions methods for the OpenApiOperation class.
13+
/// </summary>
14+
public static class OpenApiOperationExtensions
15+
{
16+
/// <summary>
17+
/// Adds a default response to the operation or 4XX/5XX responses for the errors depending on the settings.
18+
/// Also adds a 204 no content response when requested.
19+
/// </summary>
20+
/// <param name="operation">The operation.</param>
21+
/// <param name="settings">The settings.</param>
22+
/// <param name="addNoContent">Whether to add a 204 no content response.</param>
23+
public static void AddErrorResponses(this OpenApiOperation operation, OpenApiConvertSettings settings, bool addNoContent = false)
24+
{
25+
if (operation == null) {
26+
throw Error.ArgumentNull(nameof(operation));
27+
}
28+
if(settings == null) {
29+
throw Error.ArgumentNull(nameof(settings));
30+
}
31+
32+
if(operation.Responses == null)
33+
{
34+
operation.Responses = new();
35+
}
36+
37+
if(addNoContent)
38+
{
39+
operation.Responses.Add(Constants.StatusCode204, Constants.StatusCode204.GetResponse());
40+
}
41+
42+
if(settings.ErrorResponsesAsDefault)
43+
{
44+
operation.Responses.Add(Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse());
45+
}
46+
else
47+
{
48+
operation.Responses.Add(Constants.StatusCodeClass4XX, Constants.StatusCodeClass4XX.GetResponse());
49+
operation.Responses.Add(Constants.StatusCodeClass5XX, Constants.StatusCodeClass5XX.GetResponse());
50+
}
51+
}
52+
}

src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiErrorSchemaGenerator.cs

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
// ------------------------------------------------------------
55

66
using System.Collections.Generic;
7+
using System.Linq;
8+
using Microsoft.OData.Edm;
79
using Microsoft.OpenApi.Models;
810
using Microsoft.OpenApi.OData.Common;
911
using Microsoft.OpenApi.OData.Edm;
@@ -26,18 +28,20 @@ public static IDictionary<string, OpenApiSchema> CreateODataErrorSchemas(this OD
2628
{
2729
Utils.CheckArgumentNull(context, nameof(context));
2830

29-
IDictionary<string, OpenApiSchema> schemas = new Dictionary<string, OpenApiSchema>();
30-
31-
// odata.error
32-
schemas.Add("odata.error", CreateErrorSchema());
31+
return new Dictionary<string, OpenApiSchema>()
32+
{
33+
// odata.error
34+
{ "odata.error", CreateErrorSchema() },
3335

34-
// odata.error.main
35-
schemas.Add("odata.error.main", CreateErrorMainSchema());
36+
// odata.error.main
37+
{ "odata.error.main", CreateErrorMainSchema() },
3638

37-
// odata.error.detail
38-
schemas.Add("odata.error.detail", CreateErrorDetailSchema());
39+
// odata.error.detail
40+
{ "odata.error.detail", CreateErrorDetailSchema() },
3941

40-
return schemas;
42+
// odata.error.innererror
43+
{ "odata.error.innererror", CreateInnerErrorSchema(context) }
44+
};
4145
}
4246

4347
/// <summary>
@@ -70,6 +74,31 @@ public static OpenApiSchema CreateErrorSchema()
7074
};
7175
}
7276

77+
/// <summary>
78+
/// Creates the inner error schema definition. If an "InnerError" complex type is defined in the root namespace, then this type will be used as the inner error type.
79+
/// Otherwise, a default inner error type of object will be created.
80+
/// </summary>
81+
/// <param name="context">The OData to Open API context.</param>
82+
/// <returns>The inner error schema definition.</returns>
83+
public static OpenApiSchema CreateInnerErrorSchema(ODataContext context)
84+
{
85+
Utils.CheckArgumentNull(context, nameof(context));
86+
87+
var rootNamespace = context.Model.DeclaredNamespaces.OrderBy(n => n.Count(x => x == '.')).FirstOrDefault();
88+
if(!string.IsNullOrEmpty(context.Settings.InnerErrorComplexTypeName) &&
89+
!string.IsNullOrEmpty(rootNamespace) &&
90+
context.Model.FindDeclaredType($"{rootNamespace}.{context.Settings.InnerErrorComplexTypeName}") is IEdmComplexType complexType)
91+
{
92+
return context.CreateSchemaTypeSchema(complexType);
93+
}
94+
95+
return new OpenApiSchema
96+
{
97+
Type = "object",
98+
Description = "The structure of this object is service-specific"
99+
};
100+
}
101+
73102
/// <summary>
74103
/// Create <see cref="OpenApiSchema"/> for "odata.error.main".
75104
/// </summary>
@@ -86,13 +115,13 @@ public static OpenApiSchema CreateErrorMainSchema()
86115
Properties = new Dictionary<string, OpenApiSchema>
87116
{
88117
{
89-
"code", new OpenApiSchema { Type = "string" }
118+
"code", new OpenApiSchema { Type = "string", Nullable = false }
90119
},
91120
{
92-
"message", new OpenApiSchema { Type = "string" }
121+
"message", new OpenApiSchema { Type = "string", Nullable = false, }
93122
},
94123
{
95-
"target", new OpenApiSchema { Type = "string" }
124+
"target", new OpenApiSchema { Type = "string", Nullable = true }
96125
},
97126
{
98127
"details",
@@ -113,8 +142,11 @@ public static OpenApiSchema CreateErrorMainSchema()
113142
"innererror",
114143
new OpenApiSchema
115144
{
116-
Type = "object",
117-
Description = "The structure of this object is service-specific"
145+
Reference = new OpenApiReference
146+
{
147+
Type = ReferenceType.Schema,
148+
Id = "odata.error.innererror"
149+
}
118150
}
119151
}
120152
}
@@ -137,13 +169,13 @@ public static OpenApiSchema CreateErrorDetailSchema()
137169
Properties = new Dictionary<string, OpenApiSchema>
138170
{
139171
{
140-
"code", new OpenApiSchema { Type = "string" }
172+
"code", new OpenApiSchema { Type = "string", Nullable = false, }
141173
},
142174
{
143-
"message", new OpenApiSchema { Type = "string" }
175+
"message", new OpenApiSchema { Type = "string", Nullable = false, }
144176
},
145177
{
146-
"target", new OpenApiSchema { Type = "string" }
178+
"target", new OpenApiSchema { Type = "string", Nullable = true, }
147179
}
148180
}
149181
};

src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiResponseGenerator.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,24 @@ internal static class OpenApiResponseGenerator
3232
},
3333

3434
{ Constants.StatusCode204, new OpenApiResponse { Description = "Success"} },
35+
{ Constants.StatusCodeClass4XX, new OpenApiResponse
36+
{
37+
Reference = new OpenApiReference
38+
{
39+
Type = ReferenceType.Response,
40+
Id = "error"
41+
}
42+
}
43+
},
44+
{ Constants.StatusCodeClass5XX, new OpenApiResponse
45+
{
46+
Reference = new OpenApiReference
47+
{
48+
Type = ReferenceType.Response,
49+
Id = "error"
50+
}
51+
}
52+
}
3553
};
3654

3755
/// <summary>
@@ -41,8 +59,7 @@ internal static class OpenApiResponseGenerator
4159
/// <returns>The created <see cref="OpenApiResponse"/>.</returns>
4260
public static OpenApiResponse GetResponse(this string statusCode)
4361
{
44-
OpenApiResponse response;
45-
if (_responses.TryGetValue(statusCode, out response))
62+
if (_responses.TryGetValue(statusCode, out OpenApiResponse response))
4663
{
4764
return response;
4865
}

src/Microsoft.OpenApi.OData.Reader/Generator/OpenApiSchemaGenerator.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ public static IDictionary<string, OpenApiSchema> CreateSchemas(this ODataContext
5050
case EdmSchemaElementKind.TypeDefinition: // Type definition
5151
{
5252
IEdmType reference = (IEdmType)element;
53+
if(reference is IEdmComplexType &&
54+
reference.FullTypeName().EndsWith(context.Settings.InnerErrorComplexTypeName, StringComparison.Ordinal))
55+
continue;
56+
5357
schemas.Add(reference.FullTypeName(), context.CreateSchemaTypeSchema(reference));
5458
}
5559
break;
@@ -340,7 +344,7 @@ public static OpenApiSchema CreateSchemaTypeDefinitionSchema(this ODataContext c
340344
return context.CreateSchema(typeDefinition.UnderlyingType);
341345
}
342346

343-
private static OpenApiSchema CreateSchemaTypeSchema(this ODataContext context, IEdmType edmType)
347+
internal static OpenApiSchema CreateSchemaTypeSchema(this ODataContext context, IEdmType edmType)
344348
{
345349
Debug.Assert(context != null);
346350
Debug.Assert(edmType != null);

src/Microsoft.OpenApi.OData.Reader/OpenApiConvertSettings.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,16 @@ public string PathPrefix
215215
/// </summary>
216216
public bool AddEnumDescriptionExtension { get; set; } = false;
217217

218+
/// <summary>
219+
/// Gets/sets a value indicating whether the error responses should be described as a default response or as 4XX and 5XX error responses.
220+
/// </summary>
221+
public bool ErrorResponsesAsDefault { get; set; } = true;
222+
223+
/// <summary>
224+
/// Gets/Sets the name of the complex type to look for in the main namespace to use as the inner error type.
225+
/// </summary>
226+
public string InnerErrorComplexTypeName { get; set; } = "InnerError";
227+
218228
internal OpenApiConvertSettings Clone()
219229
{
220230
var newSettings = new OpenApiConvertSettings
@@ -251,6 +261,8 @@ internal OpenApiConvertSettings Clone()
251261
RequireDerivedTypesConstraintForODataTypeCastSegments = this.RequireDerivedTypesConstraintForODataTypeCastSegments,
252262
EnableDeprecationInformation = this.EnableDeprecationInformation,
253263
AddEnumDescriptionExtension = this.AddEnumDescriptionExtension,
264+
ErrorResponsesAsDefault = this.ErrorResponsesAsDefault,
265+
InnerErrorComplexTypeName = this.InnerErrorComplexTypeName
254266
};
255267

256268
return newSettings;

src/Microsoft.OpenApi.OData.Reader/Operation/ComplexPropertyDeleteOperationHandler.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,7 @@ protected override void SetParameters(OpenApiOperation operation)
5555
/// <inheritdoc/>
5656
protected override void SetResponses(OpenApiOperation operation)
5757
{
58-
operation.Responses = new OpenApiResponses
59-
{
60-
{ Constants.StatusCode204, Constants.StatusCode204.GetResponse() },
61-
{ Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse() }
62-
};
63-
58+
operation.AddErrorResponses(Context.Settings, true);
6459
base.SetResponses(operation);
6560
}
6661
protected override void SetSecurity(OpenApiOperation operation)

src/Microsoft.OpenApi.OData.Reader/Operation/ComplexPropertyGetOperationHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ protected override void SetResponses(OpenApiOperation operation)
131131
SetCollectionResponse(operation);
132132
else
133133
SetSingleResponse(operation);
134-
operation.Responses.Add(Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse());
134+
operation.AddErrorResponses(Context.Settings, false);
135135

136136
base.SetResponses(operation);
137137
}

src/Microsoft.OpenApi.OData.Reader/Operation/ComplexPropertyPatchOperationHandler.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,7 @@ protected override void SetRequestBody(OpenApiOperation operation)
8383
/// <inheritdoc/>
8484
protected override void SetResponses(OpenApiOperation operation)
8585
{
86-
operation.Responses = new OpenApiResponses
87-
{
88-
{ Constants.StatusCode204, Constants.StatusCode204.GetResponse() },
89-
{ Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse() }
90-
};
91-
86+
operation.AddErrorResponses(Context.Settings, true);
9287
base.SetResponses(operation);
9388
}
9489
protected override void SetSecurity(OpenApiOperation operation)

src/Microsoft.OpenApi.OData.Reader/Operation/ComplexPropertyPostOperationHandler.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,7 @@ protected override void SetRequestBody(OpenApiOperation operation)
9696
/// <inheritdoc/>
9797
protected override void SetResponses(OpenApiOperation operation)
9898
{
99-
operation.Responses = new OpenApiResponses
100-
{
101-
{ Constants.StatusCode204, Constants.StatusCode204.GetResponse() },
102-
{ Constants.StatusCodeDefault, Constants.StatusCodeDefault.GetResponse() }
103-
};
104-
99+
operation.AddErrorResponses(Context.Settings, true);
105100
base.SetResponses(operation);
106101
}
107102

0 commit comments

Comments
 (0)