Skip to content

Commit 98a6b29

Browse files
authored
Merge pull request #157 from microsoft/feature/collection-responses
makes collection responses reusable
2 parents dd1bcc7 + e93b7a4 commit 98a6b29

27 files changed

+3864
-5311
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,10 @@ internal static class Constants
8484
/// Name used for the OpenAPI referenced schema for OData Count operations responses.
8585
/// </summary>
8686
public static string DollarCountSchemaName = "ODataCountResponse";
87+
88+
/// <summary>
89+
/// Suffix used for collection response schemas.
90+
/// </summary>
91+
public static string CollectionSchemaSuffix = "CollectionResponse";
8792
}
8893
}

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

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,27 @@ public static IDictionary<string, OpenApiResponse> CreateResponses(this ODataCon
6262
{
6363
Utils.CheckArgumentNull(context, nameof(context));
6464

65-
return new Dictionary<string, OpenApiResponse>
65+
var responses = new Dictionary<string, OpenApiResponse>
6666
{
6767
{ "error", CreateErrorResponse() }
6868
};
69+
70+
if(context.Settings.EnableDollarCountPath)
71+
{
72+
responses[Constants.DollarCountSchemaName] = CreateCountResponse();
73+
}
74+
75+
responses = responses.Concat(context.GetAllCollectionEntityTypes()
76+
.Select(x => new KeyValuePair<string, OpenApiResponse>(
77+
$"{(x is IEdmEntityType eType ? eType.FullName() : x.FullTypeName())}{Constants.CollectionSchemaSuffix}",
78+
CreateCollectionResponse(x)))
79+
.Where(x => !responses.ContainsKey(x.Key)))
80+
.ToDictionary(x => x.Key, x => x.Value);
81+
82+
if(context.HasAnyNonContainedCollections())
83+
responses[$"String{Constants.CollectionSchemaSuffix}"] = CreateCollectionResponse("String");
84+
85+
return responses;
6986
}
7087

7188
/// <summary>
@@ -165,6 +182,61 @@ public static OpenApiResponses CreateResponses(this ODataContext context, IEdmOp
165182
return responses;
166183
}
167184

185+
private static OpenApiResponse CreateCollectionResponse(IEdmStructuredType structuredType)
186+
{
187+
var entityType = structuredType as IEdmEntityType;
188+
return CreateCollectionResponse(entityType?.FullName() ?? structuredType.FullTypeName());
189+
}
190+
private static OpenApiResponse CreateCollectionResponse(string typeName)
191+
{
192+
return new OpenApiResponse
193+
{
194+
Description = "Retrieved collection",
195+
Content = new Dictionary<string, OpenApiMediaType>
196+
{
197+
{
198+
Constants.ApplicationJsonMediaType,
199+
new OpenApiMediaType
200+
{
201+
Schema = new OpenApiSchema
202+
{
203+
Reference = new OpenApiReference
204+
{
205+
Type = ReferenceType.Schema,
206+
Id = $"{typeName}{Constants.CollectionSchemaSuffix}"
207+
}
208+
}
209+
}
210+
}
211+
}
212+
};
213+
}
214+
215+
private static OpenApiResponse CreateCountResponse()
216+
{
217+
OpenApiSchema schema = new()
218+
{
219+
Reference = new() {
220+
Type = ReferenceType.Schema,
221+
Id = Constants.DollarCountSchemaName
222+
}
223+
};
224+
return new OpenApiResponse
225+
{
226+
Description = "The count of the resource",
227+
Content = new Dictionary<string, OpenApiMediaType>
228+
{
229+
{
230+
"text/plain",
231+
new OpenApiMediaType
232+
{
233+
Schema = schema
234+
}
235+
}
236+
}
237+
};
238+
}
239+
168240
private static OpenApiResponse CreateErrorResponse()
169241
{
170242
return new OpenApiResponse

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

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,100 @@ public static IDictionary<string, OpenApiSchema> CreateSchemas(this ODataContext
7373
Format = "int32"
7474
};
7575

76+
schemas = schemas.Concat(context.GetAllCollectionEntityTypes()
77+
.Select(x => new KeyValuePair<string, OpenApiSchema>(
78+
$"{(x is IEdmEntityType eType ? eType.FullName() : x.FullTypeName())}{Constants.CollectionSchemaSuffix}",
79+
CreateCollectionSchema(context, x)))
80+
.Where(x => !schemas.ContainsKey(x.Key)))
81+
.ToDictionary(x => x.Key, x => x.Value);
82+
83+
if(context.HasAnyNonContainedCollections())
84+
schemas[$"String{Constants.CollectionSchemaSuffix}"] = CreateCollectionSchema(context, new OpenApiSchema { Type = "string" }, "string");
85+
7686
return schemas;
7787
}
88+
internal static bool HasAnyNonContainedCollections(this ODataContext context)
89+
{
90+
return context.Model
91+
.SchemaElements
92+
.OfType<IEdmStructuredType>()
93+
.SelectMany(x => x.NavigationProperties())
94+
.Any(x => x.TargetMultiplicity() == EdmMultiplicity.Many && !x.ContainsTarget);
95+
}
96+
internal static IEnumerable<IEdmStructuredType> GetAllCollectionEntityTypes(this ODataContext context)
97+
{
98+
var collectionEntityTypes = new HashSet<IEdmStructuredType>(
99+
(context.EntityContainer?
100+
.EntitySets()
101+
.Select(x => x.EntityType()) ??
102+
Enumerable.Empty<IEdmStructuredType>())
103+
.Union(context.Model
104+
.SchemaElements
105+
.OfType<IEdmStructuredType>()
106+
.SelectMany(x => x.NavigationProperties())
107+
.Where(x => x.TargetMultiplicity() == EdmMultiplicity.Many)
108+
.Select(x => x.Type.ToStructuredType()))
109+
.Distinct()); // we could include actions and functions but actions are not pageable by nature (OData.NextLink) and functions might have specific annotations (deltalink)
110+
var derivedCollectionTypes = collectionEntityTypes.SelectMany(x => context.Model.FindAllDerivedTypes(x).OfType<IEdmStructuredType>())
111+
.Where(x => !collectionEntityTypes.Contains(x))
112+
.Distinct()
113+
.ToArray();
114+
return collectionEntityTypes.Union(derivedCollectionTypes);
115+
}
116+
117+
private static OpenApiSchema CreateCollectionSchema(ODataContext context, IEdmStructuredType structuredType)
118+
{
119+
OpenApiSchema schema = null;
120+
var entityType = structuredType as IEdmEntityType;
121+
122+
if (context.Settings.EnableDerivedTypesReferencesForResponses && entityType != null)
123+
{
124+
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(entityType, context.Model);
125+
}
126+
127+
if (schema == null)
128+
{
129+
schema = new OpenApiSchema
130+
{
131+
Reference = new OpenApiReference
132+
{
133+
Type = ReferenceType.Schema,
134+
Id = entityType?.FullName() ?? structuredType.FullTypeName()
135+
}
136+
};
137+
}
138+
return CreateCollectionSchema(context, schema, entityType?.Name ?? structuredType.FullTypeName());
139+
}
140+
private static OpenApiSchema CreateCollectionSchema(ODataContext context, OpenApiSchema schema, string typeName)
141+
{
142+
var properties = new Dictionary<string, OpenApiSchema>
143+
{
144+
{
145+
"value",
146+
new OpenApiSchema
147+
{
148+
Type = "array",
149+
Items = schema
150+
}
151+
}
152+
};
153+
if (context.Settings.EnablePagination)
154+
{
155+
properties.Add(
156+
"@odata.nextLink",
157+
new OpenApiSchema
158+
{
159+
Type = "string"
160+
});
161+
}
162+
163+
return new OpenApiSchema
164+
{
165+
Title = $"Collection of {typeName}",
166+
Type = "object",
167+
Properties = properties
168+
};
169+
}
78170

79171
/// <summary>
80172
/// Create a <see cref="OpenApiSchema"/> for a <see cref="IEdmEnumType"/>.

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

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// Licensed under the MIT License (MIT). See LICENSE in the repo root for license information.
44
// ------------------------------------------------------------
55

6-
using System.Collections.Generic;
76
using System.Linq;
87
using Microsoft.OpenApi.Models;
98
using Microsoft.OpenApi.OData.Common;
@@ -55,30 +54,15 @@ protected override void SetBasicInfo(OpenApiOperation operation)
5554
/// <inheritdoc/>
5655
protected override void SetResponses(OpenApiOperation operation)
5756
{
58-
OpenApiSchema schema = new()
59-
{
60-
Reference = new() {
61-
Type = ReferenceType.Schema,
62-
Id = Constants.DollarCountSchemaName
63-
}
64-
};
65-
6657
operation.Responses = new OpenApiResponses
6758
{
6859
{
6960
Constants.StatusCode200,
7061
new OpenApiResponse
7162
{
72-
Description = "The count of the resource",
73-
Content = new Dictionary<string, OpenApiMediaType>
74-
{
75-
{
76-
"text/plain",
77-
new OpenApiMediaType
78-
{
79-
Schema = schema
80-
}
81-
}
63+
Reference = new OpenApiReference() {
64+
Type = ReferenceType.Response,
65+
Id = Constants.DollarCountSchemaName
8266
}
8367
}
8468
}

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

Lines changed: 4 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -130,69 +130,17 @@ protected override void SetParameters(OpenApiOperation operation)
130130
/// <inheritdoc/>
131131
protected override void SetResponses(OpenApiOperation operation)
132132
{
133-
OpenApiSchema schema = null;
134-
135-
if (Context.Settings.EnableDerivedTypesReferencesForResponses)
136-
{
137-
schema = EdmModelHelper.GetDerivedTypesReferenceSchema(EntitySet.EntityType(), Context.Model);
138-
}
139-
140-
if (schema == null)
141-
{
142-
schema = new OpenApiSchema
143-
{
144-
Reference = new OpenApiReference
145-
{
146-
Type = ReferenceType.Schema,
147-
Id = EntitySet.EntityType().FullName()
148-
}
149-
};
150-
}
151-
152-
var properties = new Dictionary<string, OpenApiSchema>
153-
{
154-
{
155-
"value",
156-
new OpenApiSchema
157-
{
158-
Type = "array",
159-
Items = schema
160-
}
161-
}
162-
};
163-
164-
if (Context.Settings.EnablePagination)
165-
{
166-
properties.Add(
167-
"@odata.nextLink",
168-
new OpenApiSchema
169-
{
170-
Type = "string"
171-
});
172-
}
173-
174133
operation.Responses = new OpenApiResponses
175134
{
176135
{
177136
Constants.StatusCode200,
178137
new OpenApiResponse
179138
{
180-
Description = "Retrieved entities",
181-
Content = new Dictionary<string, OpenApiMediaType>
139+
Reference = new OpenApiReference()
182140
{
183-
{
184-
Constants.ApplicationJsonMediaType,
185-
new OpenApiMediaType
186-
{
187-
Schema = new OpenApiSchema
188-
{
189-
Title = "Collection of " + EntitySet.EntityType().Name,
190-
Type = "object",
191-
Properties = properties
192-
}
193-
}
194-
}
195-
}
141+
Type = ReferenceType.Response,
142+
Id = $"{EntitySet.EntityType().FullName()}{Constants.CollectionSchemaSuffix}"
143+
},
196144
}
197145
}
198146
};

0 commit comments

Comments
 (0)