Skip to content

Commit c8d9853

Browse files
committed
Reference in top-level component is now serialized properly:
- If we don't see any reference, write full object. - If we see a Reference to itself, write the full object without $ref. - If we see a Reference to another object, always write as $ref
1 parent 0ed8e2b commit c8d9853

File tree

5 files changed

+440
-30
lines changed

5 files changed

+440
-30
lines changed

src/Microsoft.OpenApi/Models/OpenApiComponents.cs

Lines changed: 147 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -78,32 +78,170 @@ public void SerializeAsV3(IOpenApiWriter writer)
7878

7979
writer.WriteStartObject();
8080

81+
// Serialize each referenceable object as full object without reference if the reference in the object points to itself.
82+
// If the reference exists but points to other objects, the object is serialized to just that reference.
83+
8184
// schemas
82-
writer.WriteOptionalMap(OpenApiConstants.Schemas, Schemas, (w, s) => s.SerializeAsV3WithoutReference(w));
85+
writer.WriteOptionalMap(
86+
OpenApiConstants.Schemas,
87+
Schemas,
88+
(w, key, component) =>
89+
{
90+
if (component.Reference != null &&
91+
component.Reference.Type == ReferenceType.Schema &&
92+
component.Reference.Id == key)
93+
{
94+
component.SerializeAsV3WithoutReference(w);
95+
}
96+
else
97+
{
98+
component.SerializeAsV3(w);
99+
}
100+
});
83101

84102
// responses
85-
writer.WriteOptionalMap(OpenApiConstants.Responses, Responses, (w, r) => r.SerializeAsV3WithoutReference(w));
103+
writer.WriteOptionalMap(
104+
OpenApiConstants.Responses,
105+
Responses,
106+
(w, key, component) =>
107+
{
108+
if (component.Reference != null &&
109+
component.Reference.Type == ReferenceType.Response &&
110+
component.Reference.Id == key)
111+
{
112+
component.SerializeAsV3WithoutReference(w);
113+
}
114+
else
115+
{
116+
component.SerializeAsV3(w);
117+
}
118+
});
86119

87120
// parameters
88-
writer.WriteOptionalMap(OpenApiConstants.Parameters, Parameters, (w, p) => p.SerializeAsV3WithoutReference(w));
121+
writer.WriteOptionalMap(
122+
OpenApiConstants.Parameters,
123+
Parameters,
124+
(w, key, component) =>
125+
{
126+
if (component.Reference != null &&
127+
component.Reference.Type == ReferenceType.Parameter &&
128+
component.Reference.Id == key)
129+
{
130+
component.SerializeAsV3WithoutReference(w);
131+
}
132+
else
133+
{
134+
component.SerializeAsV3(w);
135+
}
136+
});
89137

90138
// examples
91-
writer.WriteOptionalMap(OpenApiConstants.Examples, Examples, (w, e) => e.SerializeAsV3WithoutReference(w));
139+
writer.WriteOptionalMap(
140+
OpenApiConstants.Examples,
141+
Examples,
142+
(w, key, component) =>
143+
{
144+
if (component.Reference != null &&
145+
component.Reference.Type == ReferenceType.Example &&
146+
component.Reference.Id == key)
147+
{
148+
component.SerializeAsV3WithoutReference(w);
149+
}
150+
else
151+
{
152+
component.SerializeAsV3(w);
153+
}
154+
});
92155

93156
// requestBodies
94-
writer.WriteOptionalMap(OpenApiConstants.RequestBodies, RequestBodies, (w, r) => r.SerializeAsV3WithoutReference(w));
157+
writer.WriteOptionalMap(
158+
OpenApiConstants.RequestBodies,
159+
RequestBodies,
160+
(w, key, component) =>
161+
{
162+
if (component.Reference != null &&
163+
component.Reference.Type == ReferenceType.RequestBody &&
164+
component.Reference.Id == key)
165+
{
166+
component.SerializeAsV3WithoutReference(w);
167+
}
168+
else
169+
{
170+
component.SerializeAsV3(w);
171+
}
172+
});
95173

96174
// headers
97-
writer.WriteOptionalMap(OpenApiConstants.Headers, Headers, (w, h) => h.SerializeAsV3WithoutReference(w));
175+
writer.WriteOptionalMap(
176+
OpenApiConstants.Headers,
177+
Headers,
178+
(w, key, component) =>
179+
{
180+
if (component.Reference != null &&
181+
component.Reference.Type == ReferenceType.Header &&
182+
component.Reference.Id == key)
183+
{
184+
component.SerializeAsV3WithoutReference(w);
185+
}
186+
else
187+
{
188+
component.SerializeAsV3(w);
189+
}
190+
});
98191

99192
// securitySchemes
100-
writer.WriteOptionalMap(OpenApiConstants.SecuritySchemes, SecuritySchemes, (w, s) => s.SerializeAsV3WithoutReference(w));
193+
writer.WriteOptionalMap(
194+
OpenApiConstants.SecuritySchemes,
195+
SecuritySchemes,
196+
(w, key, component) =>
197+
{
198+
if (component.Reference != null &&
199+
component.Reference.Type == ReferenceType.SecurityScheme &&
200+
component.Reference.Id == key)
201+
{
202+
component.SerializeAsV3WithoutReference(w);
203+
}
204+
else
205+
{
206+
component.SerializeAsV3(w);
207+
}
208+
});
101209

102210
// links
103-
writer.WriteOptionalMap(OpenApiConstants.Links, Links, (w, link) => link.SerializeAsV3WithoutReference(w));
211+
writer.WriteOptionalMap(
212+
OpenApiConstants.Links,
213+
Links,
214+
(w, key, component) =>
215+
{
216+
if (component.Reference != null &&
217+
component.Reference.Type == ReferenceType.Link &&
218+
component.Reference.Id == key)
219+
{
220+
component.SerializeAsV3WithoutReference(w);
221+
}
222+
else
223+
{
224+
component.SerializeAsV3(w);
225+
}
226+
});
104227

105228
// callbacks
106-
writer.WriteOptionalMap(OpenApiConstants.Callbacks, Callbacks, (w, c) => c.SerializeAsV3WithoutReference(w));
229+
writer.WriteOptionalMap(
230+
OpenApiConstants.Callbacks,
231+
Callbacks,
232+
(w, key, component) =>
233+
{
234+
if (component.Reference != null &&
235+
component.Reference.Type == ReferenceType.Callback &&
236+
component.Reference.Id == key)
237+
{
238+
component.SerializeAsV3WithoutReference(w);
239+
}
240+
else
241+
{
242+
component.SerializeAsV3(w);
243+
}
244+
});
107245

108246
// extensions
109247
writer.WriteExtensions(Extensions);

src/Microsoft.OpenApi/Models/OpenApiDocument.cs

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,17 +125,80 @@ public void SerializeAsV2(IOpenApiWriter writer)
125125
// paths
126126
writer.WriteRequiredObject(OpenApiConstants.Paths, Paths, (w, p) => p.SerializeAsV2(w));
127127

128+
// Serialize each referenceable object as full object without reference if the reference in the object points to itself.
129+
// If the reference exists but points to other objects, the object is serialized to just that reference.
130+
128131
// definitions
129-
writer.WriteOptionalMap(OpenApiConstants.Definitions, Components?.Schemas, (w, s) => s.SerializeAsV2WithoutReference(w));
132+
writer.WriteOptionalMap(
133+
OpenApiConstants.Definitions,
134+
Components?.Schemas,
135+
(w, key, component) =>
136+
{
137+
if (component.Reference != null &&
138+
component.Reference.Type == ReferenceType.Schema &&
139+
component.Reference.Id == key)
140+
{
141+
component.SerializeAsV2WithoutReference(w);
142+
}
143+
else
144+
{
145+
component.SerializeAsV2(w);
146+
}
147+
});
130148

131149
// parameters
132-
writer.WriteOptionalMap(OpenApiConstants.Parameters, Components?.Parameters, (w, p) => p.SerializeAsV2WithoutReference(w));
150+
writer.WriteOptionalMap(
151+
OpenApiConstants.Parameters,
152+
Components?.Parameters,
153+
(w, key, component) =>
154+
{
155+
if (component.Reference != null &&
156+
component.Reference.Type == ReferenceType.Parameter &&
157+
component.Reference.Id == key)
158+
{
159+
component.SerializeAsV2WithoutReference(w);
160+
}
161+
else
162+
{
163+
component.SerializeAsV2(w);
164+
}
165+
});
133166

134167
// responses
135-
writer.WriteOptionalMap(OpenApiConstants.Responses, Components?.Responses, (w, r) => r.SerializeAsV2WithoutReference(w));
168+
writer.WriteOptionalMap(
169+
OpenApiConstants.Responses,
170+
Components?.Responses,
171+
(w, key, component) =>
172+
{
173+
if (component.Reference != null &&
174+
component.Reference.Type == ReferenceType.Response &&
175+
component.Reference.Id == key)
176+
{
177+
component.SerializeAsV2WithoutReference(w);
178+
}
179+
else
180+
{
181+
component.SerializeAsV2(w);
182+
}
183+
});
136184

137185
// securityDefinitions
138-
writer.WriteOptionalMap(OpenApiConstants.SecurityDefinitions, Components?.SecuritySchemes, (w, s) => s.SerializeAsV2WithoutReference(w));
186+
writer.WriteOptionalMap(
187+
OpenApiConstants.SecurityDefinitions,
188+
Components?.SecuritySchemes,
189+
(w, key, component) =>
190+
{
191+
if (component.Reference != null &&
192+
component.Reference.Type == ReferenceType.SecurityScheme &&
193+
component.Reference.Id == key)
194+
{
195+
component.SerializeAsV2WithoutReference(w);
196+
}
197+
else
198+
{
199+
component.SerializeAsV2(w);
200+
}
201+
});
139202

140203
// security
141204
writer.WriteOptionalCollection(

src/Microsoft.OpenApi/Writers/OpenApiWriterExtensions.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ public static void WriteRequiredMap(
261261
/// <param name="writer">The Open API writer.</param>
262262
/// <param name="name">The property name.</param>
263263
/// <param name="elements">The map values.</param>
264-
/// <param name="action">The map element writer action.</param>
264+
/// <param name="action">The map element writer action with writer and value as input.</param>
265265
public static void WriteOptionalMap<T>(
266266
this IOpenApiWriter writer,
267267
string name,
@@ -275,6 +275,27 @@ public static void WriteOptionalMap<T>(
275275
}
276276
}
277277

278+
/// <summary>
279+
/// Write the optional Open API element map.
280+
/// </summary>
281+
/// <typeparam name="T">The Open API element type. <see cref="IOpenApiElement"/></typeparam>
282+
/// <param name="writer">The Open API writer.</param>
283+
/// <param name="name">The property name.</param>
284+
/// <param name="elements">The map values.</param>
285+
/// <param name="action">The map element writer action with writer, key, and value as input.</param>
286+
public static void WriteOptionalMap<T>(
287+
this IOpenApiWriter writer,
288+
string name,
289+
IDictionary<string, T> elements,
290+
Action<IOpenApiWriter, string, T> action)
291+
where T : IOpenApiElement
292+
{
293+
if (elements != null && elements.Any())
294+
{
295+
writer.WriteMapInternal(name, elements, action);
296+
}
297+
}
298+
278299
/// <summary>
279300
/// Write the required Open API element map.
280301
/// </summary>
@@ -326,6 +347,15 @@ private static void WriteMapInternal<T>(
326347
string name,
327348
IDictionary<string, T> elements,
328349
Action<IOpenApiWriter, T> action)
350+
{
351+
WriteMapInternal(writer, name, elements, (w, k, s) => action(w, s));
352+
}
353+
354+
private static void WriteMapInternal<T>(
355+
this IOpenApiWriter writer,
356+
string name,
357+
IDictionary<string, T> elements,
358+
Action<IOpenApiWriter, string, T> action)
329359
{
330360
CheckArguments(writer, name, action);
331361

@@ -339,7 +369,7 @@ private static void WriteMapInternal<T>(
339369
writer.WritePropertyName(item.Key);
340370
if (item.Value != null)
341371
{
342-
action(writer, item.Value);
372+
action(writer, item.Key, item.Value);
343373
}
344374
else
345375
{
@@ -361,6 +391,16 @@ private static void CheckArguments<T>(IOpenApiWriter writer, string name, Action
361391
}
362392
}
363393

394+
private static void CheckArguments<T>(IOpenApiWriter writer, string name, Action<IOpenApiWriter, string, T> action)
395+
{
396+
CheckArguments(writer, name);
397+
398+
if (action == null)
399+
{
400+
throw Error.ArgumentNull(nameof(action));
401+
}
402+
}
403+
364404
private static void CheckArguments(IOpenApiWriter writer, string name)
365405
{
366406
if (writer == null)

0 commit comments

Comments
 (0)