Skip to content

Commit 9a49cbe

Browse files
authored
Add singular component to openapi when array of singular is openapi response (CarterCommunity#192)
1 parent 1add738 commit 9a49cbe

File tree

4 files changed

+87
-16
lines changed

4 files changed

+87
-16
lines changed

samples/CarterSample/Features/CastMembers/CastMemberModule.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Carter;
44
using Carter.ModelBinding;
55
using Carter.Response;
6+
using CarterSample.Features.CastMembers.OpenApi;
67
using Microsoft.AspNetCore.Http;
78
using ValidatorOnlyProject;
89

@@ -21,6 +22,13 @@ public CastMemberModule()
2122

2223
return res.WriteAsync("OK");
2324
});
25+
26+
this.Get<GetCastMembers>("/castmembers", (request, response, routeData) =>
27+
{
28+
var castMembers = new[] { new CastMember { Name = "Samuel L Jackson" }, new CastMember { Name = "John Travolta" } };
29+
30+
return response.AsJson(castMembers);
31+
});
2432
}
2533
}
2634
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
namespace CarterSample.Features.CastMembers.OpenApi
2+
{
3+
using System.Collections.Generic;
4+
using Carter.OpenApi;
5+
using ValidatorOnlyProject;
6+
7+
public class GetCastMembers : RouteMetaData
8+
{
9+
public override string Description { get; } = "Returns a list of actors";
10+
11+
public override RouteMetaDataResponse[] Responses { get; } =
12+
{
13+
new RouteMetaDataResponse
14+
{
15+
Code = 200,
16+
Description = $"A list of {nameof(CastMember)}s",
17+
Response = typeof(IEnumerable<CastMember>)
18+
}
19+
};
20+
21+
public override string Tag { get; } = "CastMembers";
22+
23+
public override string OperationId { get; } = "CastMembers_GetCastMembers";
24+
}
25+
}

src/OpenApi/CarterOpenApi.cs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,6 @@ private static void CreateOpenApiResponseBody(OpenApiDocument document, KeyValue
218218
.Select(x => (Name: x.Name.ToLower(), Type: x.PropertyType.Name.ToLower()))
219219
.ToList();
220220

221-
var arrbj = new OpenApiArray();
222221
var propObj = new OpenApiObject();
223222

224223
foreach (var propertyInfo in propNames)
@@ -233,16 +232,6 @@ private static void CreateOpenApiResponseBody(OpenApiDocument document, KeyValue
233232
Example = propObj
234233
};
235234

236-
var arrayschema = new OpenApiSchema { Type = "array" };
237-
238-
if (arrayType)
239-
{
240-
arrbj.Add(propObj);
241-
arrayschema.Items = schema;
242-
}
243-
244-
var respObj = arrayType ? new OpenApiObject { { responseType.Name.ToLower(), arrbj } } : propObj;
245-
246235
openApiResponse = new OpenApiResponse
247236
{
248237
Description = valueStatusCode.Description,
@@ -252,9 +241,7 @@ private static void CreateOpenApiResponseBody(OpenApiDocument document, KeyValue
252241
"application/json",
253242
new OpenApiMediaType
254243
{
255-
//Example = respObj
256244
Schema = new OpenApiSchema { Reference = new OpenApiReference { Id = responseTypeName, Type = ReferenceType.Schema } }
257-
//Schema = arrayType ? arrayschema : schema
258245
}
259246
}
260247
}
@@ -268,9 +255,15 @@ private static void CreateOpenApiResponseBody(OpenApiDocument document, KeyValue
268255
}
269256
else
270257
{
258+
//Add component called "Directors" plural
271259
document.Components.Schemas.Add(responseTypeName,
272260
new OpenApiSchema { Type = "array", Items = new OpenApiSchema { Reference = new OpenApiReference { Id = singularTypeName, Type = ReferenceType.Schema } } });
273-
//TODO Should we check that at the end that any components that are "array" types have a component registered of the singularTypeName for example you could have IEnumerable<Foo> but Foo is not used in another route so won't be registered in components
261+
262+
//If not already added add component called "Director" singular
263+
if (!document.Components.Schemas.ContainsKey(singularTypeName))
264+
{
265+
document.Components.Schemas.Add(singularTypeName, schema);
266+
}
274267
}
275268
}
276269
}
@@ -449,12 +442,19 @@ private static void CreateOpenApiRequestBody(OpenApiDocument document, KeyValueP
449442

450443
operation.RequestBody = requestBody;
451444

452-
//TODO Should we document array request bodies and if so the next line applies:
453-
//TODO Should we check that at the end that any components that are "array" types have a component registered of the singularTypeName for example you could have IEnumerable<Foo> but Foo is not used in another route so won't be registered in components
454445
if (!document.Components.Schemas.ContainsKey(requestType.Name))
455446
{
456447
document.Components.Schemas.Add(requestType.Name, schema);
457448
}
449+
else
450+
{
451+
//TODO
452+
//Currently request schemas contain potentially more information in them via the validation properties.
453+
//If the response openapi processing has added the type as a component already we should override this here as it may contain more info
454+
//One solution to fix this if the response schema needs additional information is to mark each request/response schema accordingly to prevent the race condition of the response
455+
//schema getting listed with its additional data or the request schema getting listed with its validation additional data. Eg "Actors" becomes "Actors_Request" & "Actors_Response"
456+
document.Components.Schemas[requestType.Name] = schema;
457+
}
458458
}
459459
}
460460

test/Carter.Samples.Tests/OpenAPITests.Should_return_Carter_approved_OpenAPI_json.approved.txt

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,27 @@
1313
}
1414
],
1515
"paths": {
16+
"/castmembers": {
17+
"get": {
18+
"tags": [
19+
"CastMembers"
20+
],
21+
"description": "Returns a list of actors",
22+
"operationId": "CastMembers_GetCastMembers",
23+
"responses": {
24+
"200": {
25+
"description": "A list of CastMembers",
26+
"content": {
27+
"application/json": {
28+
"schema": {
29+
"$ref": "#/components/schemas/CastMembers"
30+
}
31+
}
32+
}
33+
}
34+
}
35+
}
36+
},
1637
"/actors": {
1738
"get": {
1839
"tags": [
@@ -208,6 +229,23 @@
208229
},
209230
"components": {
210231
"schemas": {
232+
"CastMembers": {
233+
"type": "array",
234+
"items": {
235+
"$ref": "#/components/schemas/CastMember"
236+
}
237+
},
238+
"CastMember": {
239+
"type": "object",
240+
"properties": {
241+
"name": {
242+
"type": "string"
243+
}
244+
},
245+
"example": {
246+
"name": ""
247+
}
248+
},
211249
"Actors": {
212250
"type": "array",
213251
"items": {

0 commit comments

Comments
 (0)